TLDR: нет обходного пути, поскольку Python ожидает определенного типа для многих специальных методов. Однако некоторые специальные методы имеют асинхронные варианты.
Функция async def
принципиально другого типа. Подобно тому, как функция генератора оценивается как генератор, функция сопрограммы оценивается как ожидаемое.
Вы можете определить специальные методы как асинхронные, но это повлияет на их возвращаемый тип. В случае __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__
,...). Их возвращаемый тип может быть любым объектом, включая ожидаемые.
Вы можете определить такие специальные методы через async def
. Это влияет на их возвращаемый тип, но требует только клиентского кода для их 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
существует несколько специальных методов, которые должны возвращать ожидаемое значение. Сюда входят __aenter__
, __aexit__
и __anext__
. Примечательно, что __await__
должен возвращать итератор, а не ожидаемое значение.
Вы можете (и в большинстве случаев должны) определить эти методы как async def
. Если вам нужны асинхронные возможности в соответствующем специальном методе синхронизации, используйте соответствующий специальный асинхронный метод с 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
person
MisterMiyagi
schedule
30.01.2020
__str__
будет асинхронным, поэтому никто не будетawait
его использовать, так что это не сработает. На самом деле это не относится к магическим методам, вы будете иметь это каждый раз, когда будете реализовывать уже определенный интерфейс. - person deceze♦   schedule 30.01.2020__str__
нет необходимости использоватьawait
для возврата отформатированной строки. - person mkrieger1   schedule 30.01.2020__str__
, не должен быть настолько сложным/дорогим, чтобы включать асинхронный вызов. - person deceze♦   schedule 30.01.2020__exit__
Метод. - person zonk   schedule 30.01.2020__enter__
и__exit__
используются для реализации менеджеров контекста. Если вы чувствуете необходимость использовать в нихawait
, вам нужен асинхронный менеджер контекста, который вы можете реализовать либо путем замены этих методов на__aenter__
и__aexit__
, либо с помощью одного из решений из вопроса, который я связал. - person mkrieger1   schedule 30.01.2020