Python asyncio:什么满足`isinstance((基于生成器的coroutune),???)== True`?(Python asyncio: what satisfies `isinstance( (generator-based coroutune), ???) == True`?)

我很困惑找不到typing.Awaitable 。 typing.Awaitable也不typing.Awaitable覆盖基于生成器的协同程序,这是一个等待定义的协程

https://www.python.org/dev/peps/pep-0492/#await-expression

从Python 3.6开始,一些asyncio API(如sleep()和open_connection()实际上返回基于生成器的协同程序。 我通常将await关键字应用于它们的返回值没有问题,但我将处理正常值和等待 s的混合,我需要弄清楚哪些需要await产生实际值。

所以这是我的问题,什么满足isinstance(c, ???) == True对于基于任意生成器的协同程序c isinstance(c, ???) == True吗? 我不坚持使用isinstance为此目的,也许getattr()可以是一个解决方案...

背景

我正在研究基于https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code的异步函数单元测试的一个小模拟实用程序,它在内部有一个asyncio.Queue()返回值的asyncio.Queue()我想增强我的实用性,以便队列可以有等待的元素,每个元素都会触发await操作。 我的代码看起来像

async def case01(loop): f = AsyncMock() f.side_effect = loop, [] await f() # blocks forever on an empty queue async def case02(loop): f = AsyncMock() f.side_effect = loop, ['foo'] await f() # => 'foo' await f() # blocks forever async def case03(loop): f = AsyncMock() f.side_effect = loop, [asyncio.sleep(1.0, 'bar', loop=loop)] await f() # yields 'bar' after 1.0 sec of delay

Background

I'm working on a tiny mock utility for unit testing of async function based on https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code which internally has a asyncio.Queue() of mocked return values, and I want to enhance my utility so that the queue can have awaitable elements, each of which triggering await operation. My code will look like

async def case01(loop): f = AsyncMock() f.side_effect = loop, [] await f() # blocks forever on an empty queue async def case02(loop): f = AsyncMock() f.side_effect = loop, ['foo'] await f() # => 'foo' await f() # blocks forever async def case03(loop): f = AsyncMock() f.side_effect = loop, [asyncio.sleep(1.0, 'bar', loop=loop)] await f() # yields 'bar' after 1.0 sec of delay

For usability reason, I don't want to manually wrap the return values with create_task().

I'm not sure my queue will ever legitimely contain normal, non-coroutine generators; still, the ideal solution should be able to distinguish normal generators from generator-based coroutines and skip applying await operation to the former.

最满意答案

检测可以传递给对象的对象的记录方式是使用inspect.isawaitable 。

根据PEP 492 , await需要一个等待对象,这可以是:

本地协程 - 用async def定义的Python函数; 一个基于生成器的协程 - 一个用@types.coroutine装饰的Python生成器; 定义__await__ Python对象; 其类型实现了tp_as_async.am_await Python / C对象。

isinstance(o, collections.abc.Awaitable)涵盖除第二个之外的所有内容。 如果没有明确记录 ,可以将其报告为Awaitable的错误,指向inspect.isawaitable以检查所有等待对象。

请注意,您无法通过检查类型来区分基于生成器的协程对象与常规生成器迭代器。 两者具有完全相同的类型,因为coroutine装饰器不包装给定的生成器,它只是在其代码对象上设置一个标志。 检查对象是否是由基于生成器的协同程序生成的生成器迭代器的唯一方法是检查其代码标志,该标志将执行如何inspect.isawaitable 。

一个相关的问题是为什么Awaitable只检查是否存在__await__而不是await其自身使用的其他机制。 对于试图使用Awaitable来检查对象的实际可用性的代码来说,这是不幸的,但它并非没有先例。 迭代性和Iterable ABC之间存在类似的差异:

class Foo: def __getitem__(self, item): raise IndexError >>> iter(Foo()) <iterator object at 0x7f2af4ad38d0> >>> list(Foo()) []

尽管Foo实例是可迭代的,但isinstance(Foo(), collections.abc.Iterable)返回false。

The documented way to detect objects that can be passed to await is with inspect.isawaitable.

According to PEP 492, await requires an awaitable object, which can be:

A native coroutine - a Python function defined with async def; A generator-based coroutine - a Python generator decorated with @types.coroutine; Python object that defines __await__; Python/C object whose type implements tp_as_async.am_await.

isinstance(o, collections.abc.Awaitable) covers all except the 2nd one. This could be reported as a bug in Awaitable if it wasn't explicitly documented, pointing to inspect.isawaitable to check for all awaitable objects.

Note that you cannot distinguish generator-based coroutine objects from regular generator-iterators by checking the type. The two have the exact same type because the coroutine decorator doesn't wrap the given generator, it just sets a flag on its code object. The only way to check if the object is a generator-iterator produced by a generator-based coroutine is to check its code flags, which how inspect.isawaitable is implemented.

A related question is why Awaitable only checks for the existence of __await__ and not for other mechanisms that await itself uses. This is unfortunate for code that tries to use Awaitable to check the actual awaitability of an object, but it is not without precedent. A similar discrepancy exists between iterability and the the Iterable ABC:

class Foo: def __getitem__(self, item): raise IndexError >>> iter(Foo()) <iterator object at 0x7f2af4ad38d0> >>> list(Foo()) []

Despite instances of Foo being iterable, isinstance(Foo(), collections.abc.Iterable) returns false.

更多推荐