从字典将* args和** kwargs传递给asyncio定期包装器函数

时间:2019-04-08 04:05:39

标签: python python-asyncio

我正在使用asyncio从字典中收集任务并执行它们,但是我很难使其按预期工作。 (这是我的问题here的后续问题,但我重新编写了一下代码,因为它没有按预期工作,因此决定改用包装器功能会更好。)

所以我正在使用包装器来调用指定的任务函数。我希望包装程序将所有* args或** kwargs转发到任务函数,并且如果设置了interval kwarg,也要定期重复执行该任务。

如何将这些信息传递给包装器和任务函数,同时又使其易于维护并能够轻松地向tasks词典中添加新任务呢?

请看一下我的代码以作说明。

import asyncio
import random

async def run_task(taskname, taskfunc, interval=None, *args, **kwargs):
    # Wrapper which will run the specified function, and repeat it if 'interval' is set.
    # Should also be able to pass any potential *args and **kwargs to the function.
    fakedelay = random.randint(1,6)
    print(f'{taskname} started (completing in {fakedelay} seconds)')
    await taskfunc(fakedelay, *args, **kwargs)
    print(f'{taskname} completed after {fakedelay} seconds')
    if interval is not None:
        print(f'Repeating {taskname} in {interval} seconds...')
        while True:
            await taskfunc(fakedelay, *args, **kwargs)
            await asyncio.sleep(interval)

async def faketask(fakedelay, *args, **kwargs):
    # Function to simulate a coroutine task
    await asyncio.sleep(fakedelay)

async def main():
    tasks = {
        # Dictionary of tasks to perform
        'Task-1': faketask,
        'Task-2': faketask,
        'Task-3': faketask,
    }

    tasklist = []
    for taskname, taskfunc in tasks.items():
        tasklist.append(run_task(taskname, taskfunc))
        print(f'Added {taskname} to job queue.')
    await asyncio.gather(*tasklist)

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

到目前为止,这似乎效果很好。但是,假设我希望Task-3在每次完成后每隔10秒重复一次。我想在tasks字典中简单地指定它,以使其尽可能简单在将来添加新任务。例如。像这样:

tasks = {
    # Dictionary of tasks to perform
    'Task-1': faketask,
    'Task-2': faketask,
    'Task-3': faketask(interval=10),
}

但是运行它可以 TypeError: faketask() missing 1 required positional argument: 'fakedelay'我认为这是有道理的,因为interval kwarg用于包装程序,而不是任务功能(faketask)本身。而且包装程序似乎无法添加任何* args或** kwargs(在这种情况下为fakedelay)。

在上一个问题中,我被建议使用functools.partial

tasks = {
    'Task-1': faketask,
    'Task-2': faketask,
    'Task-3': functools.partial(faketask, interval=10),
}

它在某种程度上解决了我上一个问题的问题,但是在重新编写代码并添加了包装函数之后,它现在似乎什么也不做,并且坦白地说,我在理解如何使用functools.partial时遇到了困难

我的问题是

  1. 我该怎么办,这是完成我想做的事情的适当方法吗?

  2. 我如何以尽可能简单的方式(为新任务可以轻松添加)为tasks词典中的特定函数提供* args和** kwargs通过包装转发到任务功能本身?

  3. 我的周期性重复函数的方法是否正确?我特别希望它仅在完成之后 睡觉,然后再重新开始,而不是即使最后一个实例尚未完成也不会再次触发。

1 个答案:

答案 0 :(得分:2)

仅当您实际包装functools.partial以包括可选的关键字参数时,才使用faketask。如果您需要将关键字参数应用于其他函数(run_task),则需要独立执行。例如,您可以在run_task字典中为tasks指定其他视蛋白:

tasks = {
    'Task-1': faketask,
    'Task-2': faketask,
    'Task-3': (faketask, {'interval': 10)),
}

然后,调用run_task的代码将需要识别元组:

for taskname, taskfunc_maybe_with_options in tasks.items():
    if isinstance(taskfunc_maybe_with_options, tuple):
        taskfunc, options = taskfunc_maybe_with_options
    else:
        taskfunc = taskfunc_maybe_with_options
        options = {}
    tasklist.append(run_task(taskname, taskfunc, **options))