Что такое генератор
Генератор — это функция, содержащая хотя бы одно ключевое слово yield. При вызове такая функция не выполняет тело сразу, а возвращает объект-генератор. Каждый вызов next() запускает код до ближайшего yield, возвращает значение и приостанавливает выполнение.
Как работает yield
def simple_generator():
print("Начало")
yield 1
print("Продолжение")
yield 2
print("Конец")
gen = simple_generator()
print(next(gen)) # Начало → 1
print(next(gen)) # Продолжение → 2
# print(next(gen)) # Конец → StopIteration
Генератор сохраняет локальные переменные и точку выполнения. Это позволяет создавать сложные ленивые последовательности без явного хранения всех элементов.
Вызов генераторной функции ничего не выполняет
Обратите внимание на первую строку примера выше: gen = simple_generator() не напечатала «Начало». Вызов только создаёт объект-генератор; тело функции начинает работать при первом next() или в начале цикла for.
gen = simple_generator() # тело ещё не выполнялось — вывода нет
print(type(gen)) # <class 'generator'>
print(next(gen)) # только теперь: Начало → 1
Это и называется ленивостью: значения вычисляются по требованию, а не заранее.
Генератор одноразовый
Дойдя до конца, генератор исчерпывается — повторно пройти по нему нельзя:
def my_range(start: int, end: int):
current = start
while current < end:
yield current
current += 1
generator = my_range(1, 4)
print(list(generator)) # [1, 2, 3]
print(list(generator)) # [] — генератор уже исчерпан
Чтобы пройти ещё раз, создайте новый генератор — вызов функции стоит дёшево:
print(list(my_range(1, 4))) # [1, 2, 3]
print(list(my_range(1, 4))) # [1, 2, 3]
Класс-итератор или генератор
Генератор и класс с __iter__/__next__ решают одну задачу разными средствами:
| Свойство | Класс-итератор | Генератор |
|---|---|---|
| Создание | __iter__() и __next__() | функция с yield |
| Состояние | храним в атрибутах объекта | Python сохраняет автоматически |
| Окончание | вручную raise StopIteration | автоматически при выходе из функции |
| Размер кода | обычно больше | обычно меньше |
| Дополнительные методы | легко добавить | менее удобно |
| Сложное состояние обхода | удобен | не всегда удобен |
Для простой последовательности значений почти всегда удобнее генератор. Класс-итератор оправдан, когда обход — полноценный объект со своим поведением и дополнительными методами.
Генераторы с параметрами
Генератор может принимать аргументы и использовать их при генерации значений:
def count_up_to(n):
count = 1
while count <= n:
yield count
count += 1
for number in count_up_to(5):
print(number) # 1 2 3 4 5
Бесконечные генераторы
Генераторы часто используются для бесконечных последовательностей. Важно предусмотреть условие остановки снаружи:
def infinite_counter(start=1):
while True:
yield start
start += 1
counter = infinite_counter()
for _ in range(5):
print(next(counter)) # 1 2 3 4 5
Метод close()
Метод close() принудительно завершает генератор. После этого вызов next() бросит StopIteration:
def sensor_reader():
for value in [10, 20, 30, 40, 50]:
yield value
sensor = sensor_reader()
for value in sensor:
print(value)
if value >= 30:
sensor.close()
break
yield from
Конструкция yield from делегирует генерацию другому итерируемому объекту или генератору:
def letters():
yield from "ABC"
print(list(letters())) # ['A', 'B', 'C']
def worker(data):
for value in data:
yield value * 2
def main(*sequences):
for seq in sequences:
yield from worker(seq)
print(list(main([1, 2], [10, 20]))) # [2, 4, 20, 40]
yield from упрощает вложенные генераторы и передаёт исключения между ними.