Search code examples
pythonasynchronousnamespacesclosurespython-asyncio

Why async function binds name incorrectly from the outer scope?


Here is an async function generator by iterating a for loop. I expected this closure to respect the names from the outer scope.

import asyncio

coroutines = []
for param in (1, 3, 5, 7, 9):

    async def coro():
        print(param ** 2)
        # await some_other_function()
    
    coroutines.append(coro)


# The below code is not async, I made a poor mistake.
# Michael Szczesny's answer fixes this.
for coro in coroutines:
    asyncio.run(coro())

While I was expecting the results to be 1, 9, 25, 49, 81 after running, here is the actual output:

81
81
81
81
81

Unexpectedly here, the name param has taken the same value each time.

Can you explain why this happens and how I can achieve the task of creating lots of async functions in a for loop with names binding correctly?


Solution

  • The included code does not run asynchronously or uses coro as a closure. Functions are evaluated at runtime in python. An asynchronous solution would look like this

    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())
    

    Output after 1 second

    1
    9
    25
    49
    81