Python asyncio:什么满足 `isinstance( (generator-based coroutune), ???) == True`?
Python asyncio: what satisfies `isinstance( (generator-based coroutune), ???) == True`?
我很困惑地发现 typing.Awaitable
和 collections.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 492,await
需要一个等待对象,可以是:
- 一个原生协程 - 用
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.isawaitable
是 implemented.
一个相关的问题是为什么 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 是错误的。
我很困惑地发现 typing.Awaitable
和 collections.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()
.
我不确定我的队列是否会合法地包含正常的非协程生成器;不过,理想的解决方案应该能够区分普通生成器和基于生成器的协程,并跳过对前者应用 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 492,await
需要一个等待对象,可以是:
- 一个原生协程 - 用
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.isawaitable
是 implemented.
一个相关的问题是为什么 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 是错误的。