В Python додадуть асинхронні генератори
В Python 3.5 була додана можливість асинхронного програмування з використанням async&await. Тепер у пітон пропонують додати асинхронні генератори.
Звичайні генератори (представлені в PEP 255) дозволили елегантно писати функції, що покроково обробляли дані та вели себе як ітератори.
Але зараз немає аналогічної концепції для асинхронного for (async for). Саме таку концепцію було запропоновано в PEP 525. Зазначається, що саме такий підхід забезпечую подвійний приріст продуктивності і порівнянні з використанням асинхронних ітераторів.
Ось так, наприклад, можна написати ітератор, що повертає числа з заданим інтервалом:
class Ticker:
"""Повертає числа від 0 до `to` кожні `delay` секунд."""
def __init__(self, delay, to):
self.delay = delay
self.i = 0
self.to = to
def __aiter__(self):
return self
async def __anext__(self):
i = self.i
if i >= self.to:
raise StopAsyncIteration
self.i += 1
if i:
await asyncio.sleep(self.delay)
return i
А ось те ж саме, але використовуючи нову концепцію асинхронних генераторів:
async def ticker(delay, to):
"""Повертає числа від 0 до `to` кожні `delay` секунд."""
for i in range(to):
yield i
await asyncio.sleep(delay)
Заманливо, чи не так?
Специфікація
В Python генератором є люба функція, що має один або більше yield:
def func(): # звичайна функція
return
def genfunc(): # генератор
yield
Пропонується використовувати схожу концепцію і для асинхронних генераторів:
async def coro(): # звичайна співпрограма
await smth()
async def asyncgen(): # асинхронний генератор
await smth()
yield 42
Результатом виклику такого генератору буде повернення об'єкту асинхронного генератора, що реалізує інтерфейс асинхронного ітератора, що описаний тут: PEP 492.
Зауважте, що при використанні не пустого return в такому генераторі буде викликано SyntaxError.
Зворотня сумісність
Новий синтаксис повністю сумісний з минулими версіями Python. При оголошенні в Python 3.5 async def функції, що містить в своєму тілі yield буде викликано SyntaxError. В Python 3.6 такий синтаксис є повністю допустимим.
Ще декілька слів про продуктивність
Відносно звичайних генераторів
При використанні CPython (еталонна реалізація інтепретатора Python) асинхронні генератори працюють так само швидко як і звичайні (був використаний таки міні бенчмарк, як нижче).
def gen():
i = 0
while i < 100000000:
yield i
i += 1
list(gen())
Відносно асинхронних ітераторів
Наступний міні-бенчмарк, показує, що асинхронні генератори приблизно в 2.3 рази швидше, ніж та сама реалізація з асинхронними ітераторами. А ще вони виглядають лаконічніше.
N = 10 ** 7
async def agen():
for i in range(N):
yield i
class AIter:
def __init__(self):
self.i = 0
def __aiter__(self):
return self
async def __anext__(self):
i = self.i
if i >= N:
raise StopAsyncIteration
self.i += 1
return i
Інші аспекти
Асинхронні генератори списків, кортежів та словників
Не дивлячись на схожу назву, це зовсім різні концепції і вона повинна бути описана в окремому PEP.
Асинхронний yield from
Реалізувати yield from в теорії можливо, але це потребує дуже сильної переробки всієї концепції генераторів. Да і немає потреби в написанні механізму для реалізації ще одного протоколу для співпрограм, що працює поверх звичайних співпрограм.
Трошки прикладів
Ось приклад, що ілюструє роботу асинхронних генераторів. Він друкує числа від 0 до 9 з інтервалом в одну секунду.
async def ticker(delay, to):
for i in range(to):
yield i
await asyncio.sleep(delay)
async def run():
async for i in ticker(1, 10):
print(i)
import asyncio
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(run())
finally:
loop.close()
251 4
Коментарі: