Возможно ли использовать несколько yield в генераторе на Python?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Множественные yield в генераторах Python
Да, полностью возможно и очень распространено использовать несколько yield в одном генераторе.
Что происходит с несколькими yield
Каждый yield выдает одно значение, и генератор паузируется в этом месте. Когда вызывается next(), выполнение продолжается со следующей строки после последнего yield.
def simple_generator():
yield 1
yield 2
yield 3
# Как это работает:
gen = simple_generator()
print(next(gen)) # 1 (выполнился первый yield)
print(next(gen)) # 2 (выполнился второй yield)
print(next(gen)) # 3 (выполнился третий yield)
print(next(gen)) # StopIteration — генератор закончился
Визуализация потока выполнения
def generator():
print("Start")
yield 1 # ПАУЗИРУЕТ (точка 1)
print("Middle")
yield 2 # ПАУЗИРУЕТ (точка 2)
print("End")
yield 3 # ПАУЗИРУЕТ (точка 3)
gen = generator()
next(gen) # Выводит "Start", потом yield 1
next(gen) # Выводит "Middle", потом yield 2
next(gen) # Выводит "End", потом yield 3
next(gen) # StopIteration
Практические примеры
1. Итерация по диапазону значений
def count_up_to(n):
i = 1
while i <= n:
yield i
i += 1
# Использование
for num in count_up_to(5):
print(num) # 1, 2, 3, 4, 5
2. Чтение файла построчно
def read_file_in_chunks(filename, chunk_size=1024):
with open(filename, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
# Использование (экономит память для больших файлов)
for chunk in read_file_in_chunks('large_file.bin', chunk_size=8192):
process(chunk)
3. Генерация последовательности
def fibonacci(n):
a, b = 0, 1
count = 0
while count < n:
yield a
a, b = b, a + b
count += 1
# Использование
for num in fibonacci(7):
print(num) # 0, 1, 1, 2, 3, 5, 8
4. Фильтрация данных
def filter_numbers(numbers, min_val, max_val):
for num in numbers:
if min_val <= num <= max_val:
yield num
# Использование
nums = [1, 5, 10, 15, 20, 25, 30]
for num in filter_numbers(nums, 10, 25):
print(num) # 10, 15, 20, 25
Yield с условиями
Очень частый паттерн — несколько yield в разных ветвях if/else:
def process_items(items):
for item in items:
if item > 0:
yield f"positive: {item}"
elif item < 0:
yield f"negative: {item}"
else:
yield "zero"
# Использование
items = [1, -2, 0, 3, -4]
for result in process_items(items):
print(result)
# positive: 1
# negative: -2
# zero
# positive: 3
# negative: -4
Yield в циклах
def flatten(nested_list):
"""Развернуть вложенные списки"""
for item in nested_list:
if isinstance(item, list):
# Вложенный список — развернуть рекурсивно
for sub_item in flatten(item):
yield sub_item
else:
# Обычный элемент — выдать
yield item
# Использование
nested = [1, [2, 3], [4, [5, 6]], 7]
for num in flatten(nested):
print(num) # 1, 2, 3, 4, 5, 6, 7
Yield в рекурсии (Tree traversal)
class TreeNode:
def __init__(self, value, left=None, right=None):
self.value = value
self.left = left
self.right = right
def in_order_traversal(node):
"""Обход дерева в порядке in-order"""
if node is None:
return
# Левое поддерево
yield from in_order_traversal(node.left)
# Сам узел
yield node.value
# Правое поддерево
yield from in_order_traversal(node.right)
# Использование
root = TreeNode(
2,
TreeNode(1),
TreeNode(3)
)
for val in in_order_traversal(root):
print(val) # 1, 2, 3
yield from (Python 3.3+)
Способ делегировать генерацию другому генератору:
def generator1():
yield 1
yield 2
def generator2():
yield 3
yield 4
def combined():
yield from generator1()
yield from generator2()
# Использование
for val in combined():
print(val) # 1, 2, 3, 4
# Эквивалент без yield from:
def combined_manual():
for val in generator1():
yield val
for val in generator2():
yield val
Отправка значений в генератор (send)
def echo():
print("Generator started")
value = yield "Ready" # Первый yield
print(f"Got: {value}")
yield "Done" # Второй yield
gen = echo()
print(next(gen)) # Generator started, выводит "Ready"
print(gen.send("Hello")) # Got: Hello, выводит "Done"
# Нужно сначала вызвать next() или send(None)
# Иначе ошибка: "can't send non-None value to a just-started generator"
Production пример: парсинг больших файлов JSON
import json
from typing import Iterator, Dict, Any
def parse_large_json_stream(filename: str, batch_size: int = 100) -> Iterator[Dict[str, Any]]:
"""
Читает большой JSON файл и выдает объекты по одному.
Экономит память для файлов с миллионами строк.
"""
batch = []
with open(filename, 'r') as f:
for line in f:
try:
obj = json.loads(line.strip())
batch.append(obj)
# Выдаем батч
if len(batch) >= batch_size:
for item in batch:
yield item
batch = []
except json.JSONDecodeError:
continue
# Выдаем оставшиеся элементы
for item in batch:
yield item
# Использование (без загрузки всего файла в память)
for record in parse_large_json_stream('millions_of_records.jsonl'):
process_record(record) # Обрабатываем по одному
Production пример: асинхронный генератор с несколькими yield
import asyncio
from typing import AsyncIterator
async def fetch_paginated_data(api_url: str) -> AsyncIterator[dict]:
"""Загружает данные со стра-по-страничного API"""
page = 1
while True:
response = await fetch(f"{api_url}?page={page}")
if not response.get('data'):
break
# Выдаем каждый элемент
for item in response['data']:
yield item
# Проверяем, есть ли следующая страница
if not response.get('has_next'):
break
page += 1
# Использование
async def main():
async for item in fetch_paginated_data("https://api.example.com/items"):
print(item)
asyncio.run(main())
Разница между return и yield
def with_yield():
yield 1
yield 2
return "done" # Возвращается как StopIteration.value
gen = with_yield()
print(next(gen)) # 1
print(next(gen)) # 2
try:
print(next(gen)) # StopIteration
except StopIteration as e:
print(f"Return value: {e.value}") # Return value: done
# В цикле for возвращаемое значение не видно:
for val in with_yield():
print(val) # 1, 2 (return значение теряется)
Performance: Generator vs List
# ❌ List — загружает всё в память
def get_numbers_list():
result = []
for i in range(1_000_000):
result.append(i)
return result
# Память: ~40MB
data = get_numbers_list()
# ✅ Generator — ленивое вычисление
def get_numbers_generator():
for i in range(1_000_000):
yield i
# Память: ~1KB (только текущее значение)
for num in get_numbers_generator():
pass
Когда использовать несколько yield
- Потоковая обработка данных (файлы, API, БД)
- Бесконечные последовательности (нельзя использовать list)
- Экономия памяти на больших датасетах
- Ленивое вычисление (вычисляется по требованию)
- Pipeline обработки (несколько трансформаций)
Итог
Да, несколько yield в генераторе не только возможны, но и очень полезны. Генераторы — один из самых мощных инструментов Python для работы с большими данными и потоковой обработки.
В production коде я использую генераторы везде, где нужна обработка большого количества данных или I/O операции. Это экономит память и делает код более читаемым.