Python asyncio:什么满足 `isinstance( (generator-based coroutune), ???) == True`?

Python asyncio: what satisfies `isinstance( (generator-based coroutune), ???) == True`?

我很困惑地发现 typing.Awaitablecollections.abc.Awaitable 都没有涵盖基于生成器的协程,它是 awaitable 中定义的协程之一

从 Python 3.6 开始,几个 asyncio API,例如 sleep()open_connection() 实际上是 return 基于生成器的协程。我通常对将 await 关键字应用到它们的 return 值没有问题,但我将处理正常值和 awaitables 和我需要弄清楚哪些需要 await 才能产生实际值。

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

背景

我正在开发一个微型模拟实用程序,用于基于 https://blog.miguelgrinberg.com/post/unit-testing-asyncio-code 的异步函数单元测试,它内部有一个 asyncio.Queue() 的模拟 return 值,我想增强我的实用程序,以便队列可以具有 awaitable 元素,每个元素都会触发 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

出于可用性原因,我不想用 create_task().

手动包装 return 值

我不确定我的队列是否会合法地包含正常的非协程生成器;不过,理想的解决方案应该能够区分普通生成器和基于生成器的协程,并跳过对前者应用 await 操作。

我不确定您要在这里测试什么,但是 the inspect module 具有用于检查大多数情况的函数,例如:

>>> async def f(c):
...     await c
>>> co = f()
>>> inspect.iscoroutinefunction(f)
True
>>> inspect.iscoroutine(co)
True
>>> inspect.isawaitable(co)
True

最后两者之间的区别在于 isawaitable 对任何你可以 await 都适用,而不仅仅是协程。

如果你真的想用isinstance测试:

isinstance(f) 只是 types.FunctionType,这不是很有用。要检查它是否是 returns 和 coroutine 的函数,您还需要检查其标志:f.__code__.co_flags & inspect.CO_COROUTINE(或者如果您不想使用 [=11=,则可以硬编码 0x80 ] 出于某种原因)。

isinstance(co)types.CoroutineType,您 可以 对其进行测试,但这可能仍然不是一个好主意。

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

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

  • 一个原生协程 - 用async def;
  • 定义的Python函数
  • 一个 generator-based 协程 - 用 @types.coroutine;
  • 修饰的 Python 生成器
  • 定义 __await__ 的 Python class 的实例;
  • 实现 tp_as_async.am_await.
  • 的扩展类型的实例

isinstance(o, collections.abc.Awaitable) 涵盖除第二个以外的所有内容。如果不是 explicitly documented,这可能会被报告为 Awaitable 中的错误,指向 inspect.isawaitable 以检查 all 可等待对象。

请注意,您无法通过检查类型来区分 generator-based 协程对象与常规 generator-iterators。两者具有完全相同的类型,因为 coroutine 装饰器不包装给定的生成器,它只是在其代码对象上设置一个标志。检查对象是否是由 generator-based 协程生成的 generator-iterator 的唯一方法是检查其代码标志,即 inspect.isawaitableimplemented.

一个相关的问题是为什么 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) returns 是错误的。