Генератор 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 к разработке поддерживаемых приложений. Вы разберётесь со структурой пакетов, объектной моделью, современной типизацией и автоматическими тестами, а затем соедините эти инструменты в небольшом приложении с хранением данных и логированием.
