Как я могу ждать внутри будущего объекта __await__?
PEP 0492 добавляет новый магический метод __await__
. Объект, реализующий этот метод, становится объектом, подобным будущему, и его можно ожидать с помощью await
. Ясно:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2)
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Хорошо, но что, если я хочу назвать определенную функцию async def
вместо asyncio.sleep
? Я не могу использовать await
, потому что __await__
не является функцией async
, я не могу использовать yield from
, потому что для встроенных сопрограмм требуется выражение await
:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
yield from new_sleep() # this is TypeError
await new_sleep() # this is SyntaxError
print('ok')
Как я могу его решить?
Ответы
Ответ 1
Использовать прямой вызов __await__()
:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
return new_sleep().__await__()
Решение было рекомендовано Юрием Селивановым (автором PEP 492) для aioodbc library
p >
Ответ 2
Краткая версия: await foo
можно заменить на yield from foo.__await__()
Объединяя все идеи из других ответов -
в простейшем случае просто делегируем другую ожидаемую работу:
def __await__(self):
return new_sleep().__await__()
Это работает, потому что метод __await__
возвращает итератор (см. PEP 492), поэтому возвращение другого итератора __await__
- это нормально.
Это означает, конечно, что мы не можем вообще изменить поведение подвески оригинала. Более общий подход состоит в том, чтобы отразить ключевое слово await
и использовать yield from
- это позволяет нам объединить несколько итераторов awaitables в один:
def __await__(self):
# theoretically possible, but not useful for my example:
#yield from something_else_first().__await__()
yield from new_sleep().__await__()
Здесь подвох: это не то же самое, что первый вариант! yield from
является выражением, поэтому, чтобы сделать то же самое, что и раньше, нам также нужно вернуть это значение:
def __await__(self):
return (yield from new_sleep().__await__())
Это напрямую отражает то, как мы будем писать правильное делегирование с использованием синтаксиса await
:
return await new_sleep()
лишний бит - какая разница между этими двумя?
def __await__(self):
do_something_synchronously()
return new_sleep().__await__()
def __await__(self):
do_something_synchronously()
return (yield from new_sleep().__await__())
Первый вариант - простая функция: когда вы ее вызываете, do_...
и возвращается итератор. Второе - это функция генератора; вызов этого не выполняет ни один из нашего кода вообще! Только когда возвращенный итератор получен впервые do_...
будет выполнен. Это имеет значение в следующей, немного надуманной ситуации:
def foo():
tmp = Waiting.__await__()
do_something()
yield from tmp
Ответ 3
Я не понимал, почему я не могу уступить из native coroutine внутри __await__
, но похоже, что можно получить от генератора сопрограммы внутри __await__
и вывести из native coroutine внутри этого генератора coroutine. Он работает:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
@asyncio.coroutine
def wrapper(coro):
return (yield from coro)
return (yield from wrapper(new_sleep()))
Ответ 4
Чтобы ждать внутри функции __await__
, используйте следующий код:
async def new_sleep():
await asyncio.sleep(1)
class Waiting:
def __await__(self):
yield from new_sleep().__await__()
print('first sleep')
yield from new_sleep().__await__()
print('second sleep')
return 'done'
Ответ 5
Используйте декоратор.
def chain__await__(f):
return lambda *args, **kwargs: f(*args, **kwargs).__await__()
Затем напишите __await__
как родную сопрограмму.
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
@chain__await__
async def __await__(self):
return await new_sleep()
Ответ 6
Вы также можете упростить версию Михаила до этого:
async def new_sleep():
await asyncio.sleep(2)
class Waiting:
def __await__(self):
async def wrapper():
await new_sleep()
print("OK")
return wrapper()
Ответ 7
Ваш оригинальный код отлично работал в Python 3.6.
Быстрое исправление (12 символов)
Исправления в других ответах впечатляют, я даже не мог поверить, что некоторые из них работают (но они делают!), Но я боюсь, что если модель asyncio
изменяться, эти исправления перестанут работать.
У меня есть минималистическое исправление, которое работает на 3.7, без оболочки:
import asyncio
class Waiting:
def __await__(self):
yield from asyncio.sleep(2).__await__()
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Единственное отличие от вашего исходного кода заключается в добавлении .__await__()
к asyncio.sleep(2)
- даже без оболочки.
Подход совместимости
Вы также можете использовать эту оболочку sync_await
для генератора, которого вы хотите await
в __await__
, вот так:
import asyncio
def sync_await(gen):
if hasattr(gen, '__await__'):
# 3.7, and user defined coroutines in 3.6
print('yield from gen.__await__()')
return (yield from gen.__await__())
else:
# 3.6, only native coroutines like asyncio.sleep()
print('yield from gen')
return (yield from gen)
class Waiting:
def __await__(self):
yield from sync_await(asyncio.sleep(2))
print('ok')
async def main():
await Waiting()
if __name__ == "__main__":
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Обратите внимание - оболочка не return (yield from...)
, а return (yield from...)
yield from
- простое делегирование итератора генератора.