习惯性地从期货的词汇中收集结果

时间:2018-01-08 13:32:25

标签: python python-3.x asynchronous python-asyncio idioms

我正在努力写一些尽可能惯用的东西来收集存储在字典中的期货的结果。

我们假设我有以下代码:

import asyncio

async def sleep(seconds):
    print(f'sleeping for {seconds} seconds')
    await asyncio.sleep(seconds)
    print(f'finished sleeping {seconds} seconds')


async def run():
    tasks = {
        '4': sleep(4),
        '3': sleep(3),
        '2': sleep(2),
        '1': sleep(1),
    }
    print(await gather_from_dict(tasks))


if __name__ == '__main__':
     asyncio.get_event_loop().run_until_complete(run())

我期待的输出是:

sleeping for 2 seconds
sleeping for 1 seconds
sleeping for 4 seconds
sleeping for 3 seconds
finished sleeping 1 seconds
finished sleeping 2 seconds
finished sleeping 3 seconds
finished sleeping 4 seconds
{'4': None, '3': None, '2': None, '1': None}

到目前为止,我找到的最干净的解决方案是:

async def gather_from_dict(tasks:Dict[Hashable, Awaitable],
                           loop=None, return_exceptions=False) -> Dict:

    results = await asyncio.gather(
        *tasks.values(),
        loop=loop,
        return_exceptions=return_exceptions
    )
    return dict(zip(tasks.keys(), results))

有关如何以更简单的方式执行此操作的任何想法? 感谢!!!

2 个答案:

答案 0 :(得分:0)

我重新定义了你的任务,使它更纯粹作为协程列表,并且更喜欢从run_until_complete方法得到结果,代码如下,并注意我在你的睡眠代码中返回一些内容,在你的代码中,你实际上返回None

import asyncio


async def sleep(seconds):
    print('sleeping for {seconds} seconds'.format(seconds=seconds))
    await asyncio.sleep(seconds)
    print('finished sleeping {seconds} seconds'.format(seconds=seconds))
    return {seconds: 'value of {seconds}'.format(seconds=seconds)}


if __name__ == '__main__':

    loop = asyncio.get_event_loop()

    tasks = [sleep(i) for i in range(1, 5)]

    finished, _ = loop.run_until_complete(
        asyncio.wait(tasks))

    result = {}

    for task in finished:
        result.update(task.result())

    print(result)
    loop.close()

答案 1 :(得分:0)

假设: 期货(或协程)将位于列表和字典的结构中,而不位于任何自定义对象或元组中!

给出如下一些异步函数:

import asyncio


async def slow(result):
    await asyncio.sleep(0.1)
    return result

async def some_complex_stuff_here():
    return {
        'a': slow(1),
        'b': [
            slow('hello'),
            {
                'c': slow('fna'),
                1: slow('world'),
            }
        ],
    }

您可以使用以下代码等待所有内容:

def __gather_futures(container, path, futures):
    if isinstance(container, dict):
        for key, value in container.items():
            sub_path = path + (key, )
            __gather_futures(value, sub_path, futures)

    if isinstance(container, list):
        for idx, value in enumerate(container):
            sub_path = path + (idx,)
            __gather_futures(value, sub_path, futures)

    if inspect.isawaitable(container):
        futures.append((path, container))


async def gather_object_futures(container):
    futures = []
    wrapper = {'content': container}
    __gather_futures(container=wrapper, path=(), futures=futures)
    results = await asyncio.gather(*[f[1] for f in futures])
    for (path, future), result in zip(futures, results):
        current_object = wrapper
        for key in path[:-1]:
            current_object = current_object[key]
        current_object[path[-1]] = result

    return wrapper['content']

要调用此方法,您可以使用:

async def run():
    return await gather_object_futures(await some_complex_stuff_here())

import time
time_start = time.time()
loop = asyncio.get_event_loop()
result = loop.run_until_complete(run())

# will print something like 100ms --> all futures have been gathred at once!
print(time.time() - time_start)  

# result contains all of the resolved results
print(result

请注意:在collect_object_futures函数调用中的等待至关重要!