Почему асинхронная функция неправильно связывает имя из внешней области?

Вот генератор асинхронных функций, повторяющий цикл 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

Звучит как дубликат закрытия с поздним связыванием

Abdul Niyas P M 27.12.2022 15:46

Чтобы исправить это, перепишите свою сопрограмму на async def coro(param=param)

Abdul Niyas P M 27.12.2022 15:48

Это не относится к сопрограммам. Это просто поведение Python по умолчанию, связанное со всеми определениями функций (и замыканиями).

Axe319 27.12.2022 16:00

Этот код не работает асинхронно и не использует coro в качестве замыкания.

Michael Szczesny 27.12.2022 16:11
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
4
85
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Только после запуска функции 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))

Я не понимаю. Это не класс.

Xiddoc 27.12.2022 16:42

Я снова обновил вопрос, чтобы было ясно, почему param не должно быть аргументом.

UpTheIrons 27.12.2022 17:22
Ответ принят как подходящий

Включенный код не работает асинхронно и не использует 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.

UpTheIrons 27.12.2022 16:44

Именно из-за дизайна вашей программы она не работает так, как вы ожидаете.

Axe319 27.12.2022 16:50

Я не понимаю вашего обновления, но я отредактировал ответ, чтобы использовать асинхронное закрытие с желаемой привязкой параметра.

Michael Szczesny 27.12.2022 16:50

@MichaelSzczesny Я снова обновил вопрос, чтобы было ясно, почему param не должно быть аргументом.

UpTheIrons 27.12.2022 17:21

@MichaelSzczesny Обновление закрытия решило эту проблему. Спасибо.

UpTheIrons 27.12.2022 17:53

Другие вопросы по теме