Вот генератор асинхронных функций, повторяющий цикл for
. Я ожидал, что это закрытие будет уважать имена из внешней области.
import asyncio
coroutines = []
for param in (1, 3, 5, 7, 9):
async def coro():
print(param ** 2)
# await some_other_function()
coroutines.append(coro)
for coro in coroutines:
asyncio.run(coro())
Хотя я ожидал, что результаты будут 1, 9, 25, 49, 81
после запуска, вот фактический результат:
81
81
81
81
81
Неожиданно здесь имя param
каждый раз принимает одно и то же значение.
Можете ли вы объяснить, почему это происходит и как я могу решить задачу создания множества асинхронных функций в цикле for
с правильной привязкой имен?
Редактировать: вот как выглядит моя программа НАМНОГО БОЛЬШЕ подробностей, хотя фактическая проблема заключается в том, что указано выше. Здесь вы увидите, почему функция coro
не должна принимать param
в качестве параметра в моем случае.
import asyncio
from dataclasses import dataclass
from typing import Any
# Using dataclass to make IDE give auto-completion suggestions.
@dataclass
class Params:
first: Any
second: Any
third: Any
fourth: Any
fifth: Any
class MyClass:
def __init__(self):
coroutines = []
for param in (1, 3, 5, 7, 9):
async def coro():
await asyncio.sleep(1)
print(param ** 2)
coroutines.append(coro)
self.run = Params(*coroutines)
async def main():
obj = MyClass()
await asyncio.gather(
obj.run.first(), # each of them should be uniquely defined...
obj.run.second(), # ...by `param` in the `for` loop.
obj.run.third(),
obj.run.fourth(),
obj.run.fifth()
)
asyncio.run(main())
Выход:
81
81
81
81
81
Чтобы исправить это, перепишите свою сопрограмму на async def coro(param=param)
Это не относится к сопрограммам. Это просто поведение Python по умолчанию, связанное со всеми определениями функций (и замыканиями).
Этот код не работает асинхронно и не использует coro в качестве замыкания.
Только после запуска функции coro
она оценит строку:
print(param ** 2)
В это время он проверит значение param
и оценит его. Так в чем тогда ценность param
?
Глядя на код, мы видим, что значение param
устанавливается во время итерации:
for param in (1, 3, 5, 7, 9):
...
# For loop is over, param is now equal to 9
# since it was the last value it iterated on
Только после этого запускается функция coro
:
for coro in coroutines:
asyncio.run(coro()) # 'param' is still set to 9, meaning that the output will always be 81
Вместо этого вы можете добавить параметр к функции coro
:
async def coro(coro_param):
print(coro_param ** 2)
... и передать параметр функции при ее вызове:
for param in (1, 3, 5, 7, 9):
asyncio.run(coro(param))
Я не понимаю. Это не класс.
Я снова обновил вопрос, чтобы было ясно, почему param
не должно быть аргументом.
Включенный код не работает асинхронно и не использует coro в качестве замыкания. Функции оцениваются во время выполнения в python
. Асинхронное решение будет выглядеть так
import asyncio
def create_task(param):
async def coro():
await asyncio.sleep(1) # make async execution noticeable
print(param ** 2)
return coro # return closure capturing param
async def main():
await asyncio.gather(*[create_task(param)() for param in (1,3,5,7,9)])
asyncio.run(main())
Вывод через 1 секунду
1
9
25
49
81
Пожалуйста, проверьте обновление по вопросу. coro
функция не должна принимать param
в качестве параметра по дизайну моей программы. Я также добавил асинхронный код в функцию coro
.
Именно из-за дизайна вашей программы она не работает так, как вы ожидаете.
Я не понимаю вашего обновления, но я отредактировал ответ, чтобы использовать асинхронное закрытие с желаемой привязкой параметра.
@MichaelSzczesny Я снова обновил вопрос, чтобы было ясно, почему param
не должно быть аргументом.
@MichaelSzczesny Обновление закрытия решило эту проблему. Спасибо.
Звучит как дубликат закрытия с поздним связыванием