How to run functions from a dict of functions whil

2019-08-20 19:29发布

问题:

I basically want to run tasks that are gathered from a dict of functions. I'm using asyncio in my program but it probably doesn't matter for my question. Here is my code to illustrate...

import asyncio
import random


async def faketask(taskname):    # This is a function to simulate asynchronous tasks being performed.
    fakedelay = random.randint(1,6)
    print(f'{taskname} started (completing in {fakedelay} seconds)')
    await asyncio.sleep(fakedelay)
    print(f'{taskname} completed after {fakedelay} seconds')


async def main():
    tasklist = {    # This is a dict of tasks to be performed
    'Task-1': faketask,
    'Task-2': faketask,
    'Task-3': faketask,
    }

    _tasks = []
    for taskname in tasklist:
        _tasks.append(asyncio.ensure_future(tasklist[taskname](taskname)))
        print(f'Added {taskname} to job queue.')
    await asyncio.gather(*_tasks)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())
    loop.close()

This works pretty well. But I want to repeat certain tasks periodically, so I added some code to the faketask function.

async def faketask(taskname, interval=None):
    fakedelay = random.randint(1,6)
    print(f'{taskname} started (completing in {fakedelay} seconds)')
    await asyncio.sleep(fakedelay)
    print(f'{taskname} completed after {fakedelay} seconds')
    if interval is not None:
        print(f'Repeating {taskname} in {interval} seconds')
        while True:
            await faketask(taskname, *args, **kwargs)
            await asyncio.sleep(interval)

This makes the function repeat by providing the kwarg interval with an integer. Future functions may also use additional *args and **kwargs.

So I basically wish I could specify the repeat interval in the tasklist dict, e.g.

    tasklist = {
    'Task-1': faketask,
    'Task-2': faketask,
    'Task-3': faketask(interval=60),
    }

But that doesn't work because faketask() is missing 1 required positional argument: 'taskname'.

Is there any clever way to solve this?

And as a bonus question, the line _tasks.append(asyncio.ensure_future(tasklist[taskname](taskname))) looks a bit ugly, is there any way to pass the taskname argument automatically?

回答1:

You can use functools.partial, which is designed exactly for situations like this:

'Task-3': functools.partial(faketask, interval=60),

And as a bonus question, the line _tasks.append(asyncio.ensure_future(tasklist[taskname](taskname))) looks a bit ugly, is there any way to pass the taskname argument automatically?

You can use items() to remove the redundancy:

tasks = []
for taskname, taskfn in tasklist.items():
    tasks.append(taskfn(taskname))

The explicit call to asyncio.ensure_future is unnecessary because asyncio.gather does that automatically. Also, I've renamed _tasks to just tasks. By convention, the _ in front of a variable name denotes that the variable will not be used in subsequent code, which is not the case here.