Создание и применение генераторов в Python: от базовых конструкций до сложных сценариев
Использование генераторов в Python
Генераторы позволяют создавать итераторы без явной реализации методов __iter__ и __next__. В основе лежит ключевое слово yield, которое приостанавливает выполнение функции, сохраняя её состояние. Такой подход экономит память и упрощает код.
Базовый генератор с yield
Как создать генератор, возвращающий последовательность чисел?
Определим функцию с yield внутри цикла. При каждом вызове next() выполнение возобновляется до следующего yield.
def countdown(n):
while n > 0:
yield n
n -= 1
# Использование
for i in countdown(5):
print(i)генератор python (генераторы в python)
5 4 3 2 1
Типичная ошибка: забыть обработать StopIteration.
Генератор автоматически вызывает исключение StopIteration, когда больше нет значений. Если вручную вызывать next() после окончания, возникнет ошибка. Используйте цикл for или обрабатывайте исключение.
gen = countdown(3)
print(next(gen)) # 3
print(next(gen)) # 2
print(next(gen)) # 1
print(next(gen)) # StopIteration
Как создать генератор в одну строку с помощью генераторного выражения?
Генераторное выражение похоже на списковое включение, но использует круглые скобки. Оно возвращает объект-генератор, а не готовый список.
squares = (x**2 for x in range(10))
print(type(squares)) # <class 'generator'>
print(list(squares)) # [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
Проблема: генераторное выражение можно использовать только один раз.
После исчерпания генератор становится пустым. Если нужно повторять, сохраните результат в список или создайте новый генератор.
gen = (x for x in [1,2,3])
print(list(gen)) # [1,2,3]
print(list(gen)) # []
Как передавать значения обратно в генератор с помощью send?
Метод send() позволяет отправить значение внутрь генератора, которое станет результатом выражения yield. Это часто используется для реализации конечных автоматов.
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
acc = accumulator()
next(acc) # запускаем до первого yield
print(acc.send(10)) # 10
print(acc.send(5)) # 15
acc.send(None) # StopIteration (завершение)
Ошибка: вызов send без предварительного вызова next.
Перед первой отправкой значения необходимо «разогреть» генератор, инициализировав его с помощью next(gen) или gen.send(None). Иначе возникнет TypeError: can't send non-None value to a just-started generator.
Как создать бесконечный генератор, избегая переполнения памяти?
Бесконечные генераторы удобны для потоковой обработки данных. Они не хранят все элементы, а выдают их по мере необходимости.
def infinite_sequence():
num = 0
while True:
yield num
num += 1
# Взять первые 5 элементов
gen = infinite_sequence()
for _ in range(5):
print(next(gen)) # 0,1,2,3,4
Риск: бесконечный цикл, если не задать условие выхода.
При использовании for без break программа зависнет. Всегда предусматривайте ограничение (например, itertools.islice).
from itertools import islice
print(list(islice(infinite_sequence(), 3))) # [0,1,2]
Как делегировать генерацию другому генератору с помощью yield from?
Ключевое слово yield from позволяет генератору частично или полностью передать управление другому итератору. Удобно при рекурсивных структурах (деревья, директории).
def internal_gen():
yield 'внутренний 1'
yield 'внутренний 2'
def outer_gen():
yield 'внешний старт'
yield from internal_gen()
yield 'внешний конец'
print(list(outer_gen()))
# ['внешний старт', 'внутренний 1', 'внутренний 2', 'внешний конец']
Проблема: ошибки внутри дочернего генератора не обрабатываются автоматически.
Если внутренний генератор выбрасывает исключение, оно распространяется наружу. Можно обернуть yield from в try/except, чтобы управлять потоком ошибок.
def faulty():
yield 'до'
raise ValueError('Ошибка!')
def safe():
try:
yield from faulty()
except ValueError as e:
yield f'Перехвачено: {e}'
print(list(safe())) # ['до', 'Перехвачено: Ошибка!']
Дополнительные примеры использования генераторов
Здесь представлены расширенные сценарии, которые демонстрируют гибкость генераторов в Python.
# Пример 1: Обход дерева в глубину (DFS) с помощью yield from
class TreeNode:
def __init__(self, value, children=None):
self.value = value
self.children = children or []
def dfs(node):
yield node.value
for child in node.children:
yield from dfs(child)
# Строим дерево: A -> B -> D, E
# -> C -> F
tree = TreeNode('A', [
TreeNode('B', [TreeNode('D'), TreeNode('E')]),
TreeNode('C', [TreeNode('F')])
])
print('DFS обход:', list(dfs(tree)))
# Результат: ['A', 'B', 'D', 'E', 'C', 'F']
# Пример 2: Pipeline генераторов для обработки данных
# Чтение строк, фильтрация, преобразование
def read_lines(filename):
with open(filename, 'r') as f:
for line in f:
yield line.rstrip('\n')
def filter_condition(lines, substring):
for line in lines:
if substring in line:
yield line
def capitalize(lines):
for line in lines:
yield line.upper()
# Допустим, файл data.txt содержит строки
# generator pipeline не требует загрузки всего файла
# pipeline = capitalize(filter_condition(read_lines('data.txt'), 'python'))
# for line in pipeline:
# print(line)
# Пример 3: Генератор для чтения больших файлов блоками
def read_chunks(file_path, chunk_size=1024):
with open(file_path, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break
yield chunk
# Использование:
# for chunk in read_chunks('bigfile.bin', 4096):
# process(chunk)
# Этот подход предотвращает загрузку всего файла в память.
# Пример 4: Комбинация нескольких итераторов с itertools.chain
from itertools import chain
def gen_a():
yield 'a1'
yield 'a2'
def gen_b():
yield 'b1'
yield 'b2'
combined = chain(gen_a(), gen_b())
print('Объединённые генераторы:', list(combined))
# Результат: ['a1', 'a2', 'b1', 'b2']
# Пример 5: Генератор с close() и обработкой исключений генератора
def controlled():
try:
while True:
received = yield
print(f'Получено: {received}')
except GeneratorExit:
print('Генератор закрыт')
gen = controlled()
next(gen)
gen.send('Значение')
gen.close()
# Вывод: Получено: Значение
# Генератор закрыт
# Пример 6: Генератор как источник корутин (asyncio не рассматриваем)
# Использование throw() для передачи исключения внутрь генератора
def custom_except():
while True:
try:
val = yield
print(f'Обработано: {val}')
except ValueError:
print('Поймано ValueError, продолжаем')
gen = custom_except()
next(gen)
gen.send(10) # Обработано: 10
gen.throw(ValueError) # Поймано ValueError, продолжаем
gen.close()
# Пример 7: Генератор для последовательности Фибоначчи с ограничением по памяти
def fibonacci(limit=None):
a, b = 0, 1
while limit is None or a < limit:
yield a
a, b = b, a + b
print('Фибоначчи до 100:', list(fibonacci(100)))
# Результат: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]