Python 如何编写高阶生成器泛型过同步?

Python 如何编写高阶生成器泛型过同步?,python,asynchronous,generator,Python,Asynchronous,Generator,我有一堆具有相同模式的代码: def process(iterable): if inspect.isasyncgen(iterable): return process_async(iterable) else: return process_normal(iterable) def process_normal(iterable): for i in iterable: do_something yie

我有一堆具有相同模式的代码:

def process(iterable):
    if inspect.isasyncgen(iterable):
        return process_async(iterable)
    else:
        return process_normal(iterable)

def process_normal(iterable):
    for i in iterable:
        do_something
        yield something

async def process_async(iterable):
    async for i in iterable:
        do_something
        yield something

这允许我用相同的代码处理普通和异步iterable,因为
process()
返回的iterable类型与它得到的类型相同。然而,当循环中的处理代码非常重要时,这会导致丑陋的代码重复,而这并不总是可以提取,因此我想知道,是否可以更简洁地编写此代码,或者用一些stdlib东西替换其中的一些代码?

您可以将处理移到函数中,并将其作为参数传递到
进程()中

您还可以将所有生成器重构为异步生成器,只使用
进程\u async()
,但如果没有更深入的知识,我不知道它是否适合您的需要

最后,在处理之前尝试将同步发电机转换为异步发电机。这还需要在转换后使用异步代码处理来自普通生成器的结果

async def to_async(iterable):
    for x in iterable:
        yield x


def process(iterable):
    if not inspect.isasyncgen(iterable):
        iterable = to_async(iterable)
    return process_async(iterable)


async def process_async(iterable):
    async for i in iterable:
        do_something
        yield something
我看到了,我想要它们
这不是解决办法,这只是一个肮脏的黑客

这里有一个使用装饰器的解决方案。这有点像黑客,因为它深入研究了异步函数和生成器在Python中的实际实现方式;这些机制记录在和中,因此它们不仅仅是CPython的实现细节,但这仍然依赖于Python实现从不添加虚假的挂起点,即使它们被判断为“无害”,这是一个有点不可靠的假设

import functools, inspect

def generic_over_async(process_async):
    def process_sync(iterable, /, *args, **kwargs):
        async def iterable_async():
            for item in iterable:
                yield item
        agen = process_async(iterable_async(), *args, **kwargs)
        sent = None
        while True:
            try:
                gen = agen.asend(sent)
                gen.send(None)
            except StopIteration as e:
                sent = yield e.value
            except StopAsyncIteration:
                return
            else:
                gen.throw(RuntimeError,
                    f"synchronously-called function '{process_async.__name__}' has blocked")
    @functools.wraps(process_async)
    def process(iterable, /, *args, **kwargs):
        if inspect.isasyncgen(iterable):
            return process_async(iterable, *args, **kwargs)
        return process_sync(iterable, *args, **kwargs)
    return process
上面定义了一个接受异步生成器并添加以下逻辑的装饰器:

  • 当使用异步生成器调用时,它会按原样传递给经过修饰的异步生成器
  • 当使用常规iterable调用时,它将转换为异步生成器并传递给修饰的异步生成器。然后手动驱动生成的异步生成器完成,并在过程中生成所有生成的值
成功使用此装饰器要求装饰函数只能等待给定的iterable和其他有效同步的
异步
函数(即,它们从不实际阻塞);否则,装饰程序将抛出
运行时错误
。确保这种情况永远不会发生,留给读者作为练习

测试用例:

import asyncio

@generic_over_async
async def process(iterable):
    async for i in iterable:
        yield i * 2

async def blow_up(iterable):
    """ Turns an iterable into an asynchronous iterable by adding dummy suspension points """
    await asyncio.sleep(0)
    for item in iterable:
        yield item
        await asyncio.sleep(0)
        
async def main():
    print(list(process(range(5))))
    print([item async for item in process(blow_up(range(5)))])

asyncio.get_event_loop().run_until_complete(main())

上面将打印两次
[0,2,4,6,8]

正如我所说的,提取转换部分并不总是可能的,因为它不一定只返回一个项目,并且使所有内容都异步有时是不可取的,例如,为了与用户提供的代码向后兼容,这些代码需要正常的可重用性。@wRAR如果您在问题中澄清,除了返回要生成的项之外,在每次迭代中还可以做些什么,那就太好了。一个简单的例子来说明为什么转换不合适,这将非常有用。@fdermishin好吧,如果它存在,我会寻找一个通用的解决方案,这就是为什么没有具体的说明
import asyncio

@generic_over_async
async def process(iterable):
    async for i in iterable:
        yield i * 2

async def blow_up(iterable):
    """ Turns an iterable into an asynchronous iterable by adding dummy suspension points """
    await asyncio.sleep(0)
    for item in iterable:
        yield item
        await asyncio.sleep(0)
        
async def main():
    print(list(process(range(5))))
    print([item async for item in process(blow_up(range(5)))])

asyncio.get_event_loop().run_until_complete(main())