Генератор Python и break: кто закрывает открытый файл

Генератор читает файл построчно через with open. Потребитель нашёл нужную строку и сделал break. Кажется, что выход из цикла должен закрыть файл, но break завершает только цикл потребителя; генератор может остаться приостановленным внутри with.

Пауза на yield сохраняет ресурс

Во время yield выполнение генератора приостанавливается вместе с локальными переменными и активными контекстами. Если сохранить ссылку на генератор, он продолжит жить после break. Не полагайтесь на момент сборки мусора: разные условия владения и реализации Python могут дать другой срок освобождения.

Контекст closing вызывает close при выходе из блока. У генератора close возобновляет завершение и даёт выполнить finally и выход из with. Потребитель тогда явно владеет сроком жизни обхода: даже если он взял только одну строку, ресурс будет освобождён на границе контекста.

Сделать закрытие наблюдаемым

В примере используется StringIO, чтобы проверить свойство closed без временных файлов. Генератор работает с ресурсом так же, как с текстовым файлом. После break, но до выхода из closing, поток открыт. Снаружи он уже закрыт. Обе проверки важны: они показывают именно границу владения.

Если функция возвращает готовый список строк, такого растянутого срока жизни нет: чтение завершается внутри вызова. Генератор экономит память и поддерживает раннее прекращение, но вместе с этим передаёт потребителю обязанность правильно закончить обход.

from contextlib import closing
from io import StringIO

opened = []
def lines():
    with StringIO("first\nsecond\n") as source:
        opened.append(source)
        for line in source:
            yield line.strip()

with closing(lines()) as iterator:
    for line in iterator:
        assert line == "first"
        break
    assert not opened[0].closed
assert opened[0].closed
assert list(iterator) == []
print("early iteration closed explicitly")

Выбрать ясный внешний интерфейс

Для функции библиотеки можно возвращать контекстный менеджер, который выдаёт итератор. Тогда требование with видно в интерфейсе. Другой вариант — принимать callback и владеть чтением внутри функции, но он менее удобен для обычных циклов. Выбор зависит от того, кто должен контролировать остановку.

Не возвращайте генератор, который обращается к файлу, уже закрытому внешним with. Это противоположная ошибка: ресурс заканчивается слишком рано. Правильная граница охватывает фактическое чтение, а не только создание объекта генератора.

Проверить специальные методы и срок жизни

В открытой практике pytest закрепите успешный результат и граничный случай. Для своего чтения добавьте ранний break и проверку освобождения. Откройте программу курса, чтобы продолжить файловыми операциями и их тестированием.

Продолжить на практике

  • Python для продвинутых: ООП, типизация и тестирование ↗

    Практический курс-мост от базового Python к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.