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

我很困惑,找不到打字.等待也没有collections.abc.Awaitable涵盖基于生成器的协同程序,这是一个等待的定义

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

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

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

背景

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

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()手动包装返回值.

我不确定我的队列是否合法地包含正常的非协同生成器;仍然,理想的解决方案应该能够区分正常的发电机和基于发电机的协同程序,并跳过对前者应用等待操作.

最佳答案 检测到可以传递给等待的对象的文档记录方法是
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)涵盖除第二个之外的所有内容.如果它不是explicitly documented,则可以将此报告为Awaitable中的错误,指向inspect.isawaitable以检查所有等待的对象.

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

一个相关的问题是为什么Awaitable只检查是否存在__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.

点赞