← Назад к вопросам
Что произойдет в конце генератора на Python?
3.0 Senior🔥 131 комментариев
#DevOps и инфраструктура#Django
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Что происходит в конце генератора Python
Когда генератор достигает конца, происходит несколько важных событий. Это часто неправильно понимается.
Базовое поведение: StopIteration
def simple_generator():
yield 1
yield 2
yield 3
# Конец генератора
gen = simple_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 3
print(next(gen)) # ❌ StopIteration исключение!
# Трассировка:
# Traceback (most recent call last):
# File "...", line 10, in <module>
# print(next(gen))
# StopIteration
StopIteration Exception
Это не ошибка — это нормальное завершение. for loop ловит это исключение:
def simple_generator():
yield 1
yield 2
yield 3
# ✅ For loop ловит StopIteration автоматически
for value in simple_generator():
print(value) # 1, 2, 3
# Никакой ошибки!
# Эквивалентно:
gen = simple_generator()
while True:
try:
print(next(gen))
except StopIteration:
break # Нормальный выход
Возвращение значения из генератора
Gенератор может вернуть значение при завершении:
def generator_with_return():
yield 1
yield 2
return 'Done!' # Возвращаемое значение
gen = generator_with_return()
print(next(gen)) # 1
print(next(gen)) # 2
try:
print(next(gen)) # StopIteration
except StopIteration as e:
print(f"Return value: {e.value}") # 'Done!'
Важно: Значение return передается в атрибут value исключения StopIteration.
Практический пример: парсинг данных
def parse_csv(filename: str):
"""Генератор для парсинга CSV файла с итоговой статистикой"""
total_lines = 0
total_bytes = 0
try:
with open(filename) as f:
for line in f:
total_bytes += len(line)
total_lines += 1
yield line.strip()
finally:
# Возвращаем статистику
return {'lines': total_lines, 'bytes': total_bytes}
# Использование
try:
gen = parse_csv('data.csv')
for row in gen:
print(row)
except StopIteration as e:
print(f"Processing complete: {e.value}")
# Или через send/throw
try:
gen = parse_csv('data.csv')
while True:
row = next(gen)
process(row)
except StopIteration as e:
stats = e.value
print(f"Processed {stats['lines']} lines, {stats['bytes']} bytes")
Generator с finally блоком
Когда генератор завершается, выполняется finally блок:
def generator_with_cleanup():
resource = open('file.txt')
try:
for line in resource:
yield line
finally:
# Это выполнится ВСЕГДА при завершении генератора
resource.close()
print("Resource cleaned up!")
# Использование
for line in generator_with_cleanup():
print(line)
# "Resource cleaned up!" будет выведено в конце
# Даже если прервать через break
for line in generator_with_cleanup():
print(line)
break # finally всё равно выполнится!
Generator.close() - явное закрытие
def generator():
try:
yield 1
yield 2
yield 3
finally:
print("Generator closed!")
gen = generator()
print(next(gen)) # 1
gen.close() # Явно закрываем
# "Generator closed!" выводится
# После close() генератор больше не работает
try:
next(gen) # StopIteration
except StopIteration:
print("Generator is exhausted")
Generator.throw() - выброс исключения
Можно выбросить исключение в генератор:
def generator_with_error_handling():
try:
yield 1
yield 2
yield 3
except ValueError as e:
print(f"Caught error: {e}")
yield 'error_handled'
finally:
print("Cleanup")
gen = generator_with_error_handling()
print(next(gen)) # 1
print(next(gen)) # 2
try:
gen.throw(ValueError("Something wrong!"))
# Исключение выброшено внутрь генератора
# Выведет: "Caught error: Something wrong!"
print(next(gen)) # 'error_handled'
except StopIteration:
print("Generator finished")
finally:
print("Done")
Бесконечный генератор
def infinite_counter():
"""Бесконечный генератор"""
n = 0
while True: # Никогда не заканчивается!
yield n
n += 1
gen = infinite_counter()
for i in range(5):
print(next(gen)) # 0, 1, 2, 3, 4
# Если вызвать next() без ограничения, он будет работать вечно
# Чтобы остановить, используем:
for i, value in enumerate(infinite_counter()):
print(value)
if i >= 9:
break # Останавливаем цикл
Delegation: yield from
Делегирование другому генератору:
def sub_generator():
yield 1
yield 2
return 'sub_done'
def main_generator():
result = yield from sub_generator()
# result = 'sub_done' (значение return из sub_generator)
yield result
return 'main_done'
try:
gen = main_generator()
print(next(gen)) # 1
print(next(gen)) # 2
print(next(gen)) # 'sub_done'
next(gen) # StopIteration
except StopIteration as e:
print(f"Final return: {e.value}") # 'main_done'
Generator Expression (похож на список)
# Генератор выражение (как список, но ленивый)
gen = (x ** 2 for x in range(5))
for value in gen:
print(value) # 0, 1, 4, 9, 16
# Попытка использовать второй раз — ничего не будет!
for value in gen:
print(value) # Ничего (генератор исчерпан)
Практическое применение: обработка больших файлов
def read_large_file(filepath: str, chunk_size: int = 8192):
"""Читаем большой файл по частям"""
bytes_read = 0
try:
with open(filepath, 'rb') as f:
while True:
chunk = f.read(chunk_size)
if not chunk:
break # Конец файла — генератор завершается
bytes_read += len(chunk)
yield chunk
finally:
# Гарантированное закрытие файла
print(f"Processed {bytes_read} bytes")
# Использование
for chunk in read_large_file('huge_file.bin'):
process_chunk(chunk)
# "Processed XXX bytes" выведется в конце
Что происходит в деталях
Жизненный цикл генератора:
1. Создание: gen = my_generator()
- Генератор не выполняется, только создается frame
2. Первый next(gen)
- Код начинает выполняться
- Доходит до yield 1
- Приостанавливается и возвращает 1
3. Второй next(gen)
- Возобновляется после yield 1
- Выполняется код между yield'ами
- Доходит до yield 2
- Приостанавливается и возвращает 2
4. Третий next(gen)
- Возобновляется после yield 2
- Достигает конца функции (или return)
- Выбрасывается StopIteration (с value если был return)
- Выполняется finally блок
5. После завершения
- Дальнейшие next() вызовы выбрасывают StopIteration
- close() уже не нужен (но вызов safe)
Сравнение с обычной функцией
# Обычная функция
def regular_function():
result = []
result.append(1)
result.append(2)
result.append(3)
return result # Все значения в памяти!
# Генератор
def generator_function():
yield 1 # Одно значение в памяти
yield 2 # Освобождает предыдущее
yield 3 # Освобождает предыдущее
# Конец, StopIteration
# Разница:
# regular_function() → [1, 2, 3] (3 элемента в памяти одновременно)
# generator_function() → 1, затем 2, затем 3 (1 элемент в памяти всегда)
Best Practices
-
Всегда используй for loop для генераторов
for value in gen: # ✓ process(value) try: while True: value = next(gen) # ❌ более сложно process(value) except StopIteration: pass -
Используй finally для очистки
def gen(): resource = acquire_resource() try: yield resource finally: release_resource(resource) -
Возвращай значение при необходимости
def process_data(): # обработка return {'processed': 100, 'errors': 0} -
Не забывай о close() для долгоживущих генераторов
gen = some_long_running_generator() try: # использование finally: gen.close()
В конце генератор просто выбрасывает StopIteration — это нормально, это конец.