魔术方法中的协程?

Coroutines in Magic Methods?

比方说,我在 Python 3.6 中有一个神奇的方法,例如:

def __str__(self):
    return 'a {self.color} car'.format(self=self)

现在我想在其中添加一个协程,比如:

async def __str__(self):
    await self.my_coro(args)
    return 'a {self.color} car'.format(self=self)

据我所知,有必要在def之前添加async,但这似乎不适用于magic Methods。是否有(简单的)解决方法或完全不可能?

TLDR:没有解决方法,因为 Python 需要许多特殊方法的特定类型。但是,一些特殊方法具有异步变体。


async def 函数从根本上说是一种不同的函数。与生成器函数对生成器求值的方式类似,协程函数对可等待对象求值。

您可以将特殊方法定义为异步,但这会影响它们的 return 类型。在 __str__ 的情况下,您会得到一个 Awatable[str] 而不是一个简单的 str。因为这里Python需要一个str,所以会报错。

>>> class AStr:
...     async def __str__(self):
...         return 'Hello Word'
>>> str(AStr())

TypeError: __str__ returned non-string (type coroutine)

这会影响由 Python 直接解释的所有特殊方法:这些包括 __str____repr____bool____len____iter____enter__ 和其他一些。通常,如果一个特殊方法涉及一些内部使用的功能(例如 str 用于直接显示)或语句(例如 for 需要一个迭代器)它不能是 async.


一些特殊的方法没有被Python直接解释。示例包括算术运算符(__add____sub__、...)、比较(__lt____eq__、...)和查找(__get____getattribute__, ...)。它们的 return 类型可以是任何对象,包括 awaitables。

您可以通过async def定义这样的特殊方法。这会影响它们的 return 类型,但只需要客户端代码来 await 它们。例如,您可以定义 + 用作 await (a + b).

>>> def AWAIT(awaitable):
...     """Basic event loop to allow synchronous ``await``"""
...     coro = awaitable.__await__()
...     try:
...         while True:
...             coro.send(None)
...     except StopIteration as e:
...         return e.args[0] if e.args else None
...
>>> class APlus:
...     def __init__(self, value):
...         self.value = value
...     async def __add__(self, other):
...         return self.value + other
...
>>> async def add(start, *values):
...     total = start
...     for avalue in map(APlus, values):
...         total = await (avalue + total)
...     return total
...
>>> AWAIT(add(5, 10, 42, 23))
80

await 机器存在一些特殊方法,预计 return 可等待。这包括 __aenter____aexit____anext__。值得注意的是,__await__ 必须 return 一个迭代器,而不是一个等待对象。

您可以(并且在大多数情况下应该)将这些方法定义为 async def。如果需要对应sync特殊方法中的异步能力,使用对应async特殊方法async def。例如,您可以定义一个异步上下文管理器。

>>> class AContext:
...     async def __aenter__(self):
...         print('async enter')
...     async def __aexit__(self, exc_type, exc_val, exc_tb):
...         print('async exit')
...
>>> async def scoped(message):
...     async with AContext():
...         print(message)
...
>>> AWAIT(scoped("Hello World"))
async enter
Hello World
async exit