← Назад к вопросам
Как взаимодействуют между собой генератор и корутина?
2.2 Middle🔥 141 комментариев
#Python Core#Асинхронность и многопоточность
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Взаимодействие генератора и корутины: от yield к async/await
Генераторы и корутины — два ключевых концепта для понимания асинхронного программирования в Python. Корутины эволюционировали из генераторов и сохраняют много общих черт, но имеют принципиально разные применения.
1. Генератор: основы
Что такое генератор?
# Функция с yield — это генератор
def simple_generator():
print("Start")
yield 1
print("After 1")
yield 2
print("After 2")
yield 3
print("Done")
# Генератор не выполняется сразу
gen = simple_generator()
print(type(gen)) # <class generator>
# Вызов next() выполняет код до следующего yield
print(next(gen)) # Start | 1
print(next(gen)) # After 1 | 2
print(next(gen)) # After 2 | 3
print(next(gen)) # Done | StopIteration
Характеристики генератора:
- Ленивое вычисление (lazy evaluation)
- Сохраняет состояние между вызовами
- Используется для итерации
yieldвозвращает значение и приостанавливает выполнение
Двусторонняя коммуникация через send()
def bidirectional_generator():
print("Generator started")
x = yield 1 # yield возвращает 1, ждёт send()
print(f"Received: {x}")
y = yield 2
print(f"Received: {y}")
yield 3
gen = bidirectional_generator()
print(next(gen)) # Generator started | 1
print(gen.send(10)) # Received: 10 | 2
print(gen.send(20)) # Received: 20 | 3
print(gen.send(30)) # StopIteration
# Вывод:
# Generator started
# 1
# Received: 10
# 2
# Received: 20
# 3
Это критично: generator может получать значения и менять своё поведение!
2. Корутина: генератор на основе yield from
yield from: делегирование
# Классический способ (Python 3.3+): generator-based coroutine
def sub_generator():
yield 1
yield 2
return 3 # Значение вернётся из yield from
def delegating_generator():
result = yield from sub_generator()
print(f"Sub-gen returned: {result}")
yield 4
gen = delegating_generator()
for value in gen:
print(value) # 1, 2, 4
# Вывод:
# 1
# 2
# Sub-gen returned: 3
# 4
Ключевое различие:
yieldпередаёт значение и ждёт next()yield fromполностью делегирует управление другому генератору- Корутина может вызывать другие корутины через
yield from
3. Эволюция: async/await (современные корутины)
Native Coroutines vs Generator-based
# ❌ Generator-based coroutine (устаревает, Python 3.3-3.9)
import asyncio
@asyncio.coroutine # Deprecated
def old_style_coro():
yield from asyncio.sleep(1)
return "Done"
# ✅ Native coroutine (Python 3.5+, рекомендуется)
async def modern_coro():
await asyncio.sleep(1)
return "Done"
# Синтаксис различен, но идея похожа
async/await расширяет концепцию генераторов
import asyncio
async def fetch_data(url: str):
print(f"Fetching {url}")
await asyncio.sleep(2) # Симуляция HTTP запроса
print(f"Done {url}")
return f"Data from {url}"
# async function — это корутина
coro = fetch_data("http://example.com")
print(type(coro)) # <class coroutine>
# asyncio.run() выполняет корутину (как event loop)
result = asyncio.run(fetch_data("http://example.com"))
print(result)
# Вывод:
# Fetching http://example.com
# Done http://example.com
# Data from http://example.com
4. Глубокая взаимосвязь: что происходит под капотом
Генератор в Event Loop (как это работает)
# Event loop управляет корутинами как если бы это были генераторы
import asyncio
async def task1():
print("Task 1 start")
await asyncio.sleep(0.5)
print("Task 1 end")
return "Result 1"
async def task2():
print("Task 2 start")
await asyncio.sleep(0.3)
print("Task 2 end")
return "Result 2"
async def main():
# Запускаем обе задачи параллельно
results = await asyncio.gather(task1(), task2())
print(results)
asyncio.run(main())
# Вывод (заметь порядок):
# Task 1 start
# Task 2 start
# Task 2 end <- task2 завершилась первой (0.3s)
# Task 1 end <- task1 завершилась второй (0.5s)
# ["Result 1", "Result 2"]
Как это работает:
- Event loop создаёт две корутины
- Запускает task1(), пока не встретит
await - Встретил
await asyncio.sleep(0.5)— приостанавливает task1 - Запускает task2(), пока не встретит
await - Встретил
await asyncio.sleep(0.3)— приостанавливает task2 - Ждёт, когда один из таймеров закончится
- task2 закончилась (0.3s < 0.5s) — возобновляет task2
- task2 завершилась
- task1 закончилась (0.5s) — возобновляет task1
- task1 завершилась
Визуализация: где yield и await?
# Под капотом asyncio обрабатывает корутины как генераторы
class EventLoop:
def __init__(self):
self.tasks = [] # Очередь задач
self.timers = {} # Таймеры
def run_until_complete(self, coro):
task = Task(coro)
self.tasks.append(task)
while self.tasks or self.timers:
# Выполняем задачу
task = self.tasks.pop(0)
try:
# Корутина — это генератор
# next(coro) == await что-то
result = next(task.coro)
# Если это таймер, добавляем в очередь
if isinstance(result, Timer):
self.timers[task] = result
else:
self.tasks.append(task)
except StopIteration as e:
# Корутина завершилась
task.result = e.value
# На практике asyncio делает это сложнее, но идея та же
5. Практические примеры взаимодействия
Пример 1: Последовательное выполнение
import asyncio
async def step1():
print("Step 1: начало")
await asyncio.sleep(1)
print("Step 1: конец")
return "Result 1"
async def step2(prev_result):
print(f"Step 2: получил {prev_result}")
await asyncio.sleep(1)
print("Step 2: конец")
return "Result 2"
async def sequential():
# ❌ Неправильно: параллельно
# await asyncio.gather(step1(), step2(None)) # 2 секунды
# ✅ Правильно: последовательно
r1 = await step1()
r2 = await step2(r1)
return r1, r2
asyncio.run(sequential())
# Вывод:
# Step 1: начало
# Step 1: конец
# Step 2: получил Result 1
# Step 2: конец
Пример 2: Обработка множества запросов
import asyncio
import aiohttp # Асинхронный HTTP клиент
async def fetch_url(session, url):
async with session.get(url) as response:
return await response.json()
async def fetch_multiple(urls):
# session — асинхронный контекстный менеджер
async with aiohttp.ClientSession() as session:
# Запускаем все запросы параллельно
tasks = [fetch_url(session, url) for url in urls]
results = await asyncio.gather(*tasks)
return results
# Использование
urls = [
"https://api.github.com/users/guido",
"https://api.github.com/users/gvanrossum",
]
results = asyncio.run(fetch_multiple(urls))
Пример 3: Генератор как источник данных для корутины
async def process_data():
# Генератор создаёт данные
def data_generator():
for i in range(5):
yield i * 2
# Корутина обрабатывает данные
for value in data_generator():
print(f"Processing: {value}")
await asyncio.sleep(0.5) # Имитация обработки
print(f"Done: {value}")
asyncio.run(process_data())
# Вывод:
# Processing: 0
# Done: 0
# Processing: 2
# Done: 2
# Processing: 4
# Done: 4
6. Таблица: Генератор vs Корутина vs async/await
| Аспект | Генератор | Корутина (yield from) | Async/Await |
|---|---|---|---|
| Синтаксис | yield | yield from | async/await |
| Использование | Итерация, ленивость | Асинхронный код (старый стиль) | Асинхронный код (рекомендуется) |
| Управление | next(), send() | next(), send() (опосредованно) | Event loop |
| Двусторонняя коммуникация | Да (send()) | Да | Да (return в await) |
| Может ждать другую? | Нет (только iterate) | Да (yield from) | Да (await) |
| Исключения | Можно бросать через throw() | Можно бросать | Можно бросать (try/except) |
| Производительность | Высокая (CPU-bound) | Средняя (I/O-bound) | Высокая (I/O-bound) |
7. Ключевые принципы взаимодействия
# 1. Корутина — это более сложный генератор
def is_generator(obj):
import types
return isinstance(obj, types.GeneratorType)
def is_coroutine(obj):
import asyncio
return asyncio.iscoroutine(obj)
# Генератор
gen = (x for x in range(3))
print(is_generator(gen)) # True
print(is_coroutine(gen)) # False
# Корутина
async def coro():
return 1
print(is_generator(coro())) # False
print(is_coroutine(coro())) # True
# 2. Event loop требует корутины (или futures, tasks)
# 3. Генератор может быть обёрнут в корутину
# 4. await ждёт только coroutine/future/task
# 5. yield может вернуть любое значение
Резюме
Генератор:
- Функция с
yield - Ленивое вычисление через итерацию
- Может общаться с caller через send()
Корутина:
- Генератор на основе
yield from - Может вызывать другие корутины
- Управляется event loop
Async/Await:
- Синтаксический сахар для корутин (Python 3.5+)
awaitвместоyield from- Event loop автоматически управляет параллелизмом
Ключевая идея: Асинхронное программирование в Python основано на том же принципе, что и генераторы — приостановка и возобновление выполнения. Event loop управляет множеством корутин, как if они были генераторами в памяти.