jim jarnac
jim jarnac

Reputation: 5152

Python - Asyncio - pass list of argument to function defined with *

Let's consider the following example from the documentation:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number+1):
        print("Task %s: Compute factorial(%s)..." % (name, i))
        await asyncio.sleep(1)
        f *= i
    print("Task %s: factorial(%s) = %s" % (name, number, f))

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    factorial("A", 2),
    factorial("B", 3),
    factorial("C", 4),
))
loop.close()

The gather function is presented as such in the module:

asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)

It works all fine, but for my real life problem I need to pass in the gather function not a multiplicity of functions with hardcoded arguments, but rather a tuple comprehension of some form creating the multiple functions.

I tried:

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.gather(
    [factorial(str(g),g) for g in range(3)]
))
loop.close()

but it doesnt work. Anyone knows how to have that gather function to work with a programatically created list of functions?

TO BE CLEAR: the gather function is not defined by me so i cannot remove the * from its definition and simply pass the list of arguments like that. I would need to "unpack" the list but i don't know how.

Upvotes: 6

Views: 17907

Answers (3)

Mark Andersen
Mark Andersen

Reputation: 1028

List of coroutines can be dynamically generated and passed as follows:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    a = factorial("A", 2)
    b = factorial("B", 3)
    c = factorial("C", 4)
    #
    # example passing in list of coroutines
    #
    await asyncio.gather(*[a,b,c])

asyncio.run(main())

Upvotes: 2

Yuval Pruss
Yuval Pruss

Reputation: 9836

You also can use the itertools.starmap for this task:

import asyncio
import itertools

async def factorial(name, number):
    f = 1
    for i in range(2, number+1):
        print("Task %s: Compute factorial(%s)..." % (name, i))
        await asyncio.sleep(1)
        f *= i
    print("Task %s: factorial(%s) = %s" % (name, number, f))

loop = asyncio.get_event_loop()

args = [(str(g), g) for g in range(3)]
tasks = itertools.starmap(factorial, args)
loop.run_until_complete(asyncio.gather(*tasks))
loop.close()

Make an iterator that computes the function using arguments obtained from the iterable. Used instead of map() when argument parameters are already grouped in tuples from a single iterable (the data has been “pre-zipped”).

For more reading: here

Upvotes: 5

jim jarnac
jim jarnac

Reputation: 5152

Found the solution:

loop.run_until_complete(asyncio.gather(*(factorial(str(g),g) for g in range(3)))

Upvotes: 5

Related Questions