← Назад к вопросам

Что произойдет в конце генератора на 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

  1. Всегда используй for loop для генераторов

    for value in gen:  # ✓
        process(value)
    
    try:
        while True:
            value = next(gen)  # ❌ более сложно
            process(value)
    except StopIteration:
        pass
    
  2. Используй finally для очистки

    def gen():
        resource = acquire_resource()
        try:
            yield resource
        finally:
            release_resource(resource)
    
  3. Возвращай значение при необходимости

    def process_data():
        # обработка
        return {'processed': 100, 'errors': 0}
    
  4. Не забывай о close() для долгоживущих генераторов

    gen = some_long_running_generator()
    try:
        # использование
    finally:
        gen.close()
    

В конце генератор просто выбрасывает StopIteration — это нормально, это конец.