魔术方法中的协程?
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
比方说,我在 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