Чтение блоками в Python: как работает iter с функцией и стоп-значением

Файл выгрузки нужно передать небольшими порциями: подсчитать хеш, отправить блоки в архив или записать в другой поток. Обычный цикл while True содержит одни и те же действия: прочитать, проверить конец, обработать. При изменении кода проверку легко поставить после обработки и включить пустой блок в статистику либо ошибочно отбросить короткий последний блок.

Выбрать признак конца потока

Двухаргументный iter принимает функцию без обязательных аргументов и значение остановки. Он вызывает функцию заново при запросе следующего элемента. Когда возвращённое значение сравнивается равным sentinel, обход заканчивается, и это значение уже не передаётся потребителю. Для бинарного read таким признаком является b"". Последний неполный блок, например b"ij", остаётся обычными данными.

Методу read нужен размер, поэтому functools.partial связывает его заранее. Полученная функция не требует аргументов и подходит iter. Здесь нет предварительного чтения всего файла: каждый следующий вызов цикла запрашивает ещё одну порцию. Размер порции управляет числом операций ввода-вывода и величиной текущего блока, но итоговый список в примере собирается только для наглядной проверки.

Пример использует BytesIO, чтобы вход был воспроизводимым и не зависел от путей на компьютере. Десять байтов делятся на четыре, четыре и два. Мы независимо проверяем состав блоков, восстановление исходного содержимого и число обработанных байтов. Эти свойства показывают, что остановка не съела хвост и не добавила пустые данные.

Прочитать неполный хвост

from functools import partial
from io import BytesIO

payload = b"abcdefghij"
with BytesIO(payload) as source:
    chunks = list(iter(partial(source.read, 4), b""))

assert chunks == [b"abcd", b"efgh", b"ij"]
assert b"".join(chunks) == payload
assert sum(map(len, chunks)) == len(payload)

with BytesIO(b"") as empty:
    assert list(iter(partial(empty.read, 4), b"")) == []

print([len(chunk) for chunk in chunks])
print(b"".join(chunks).decode("ascii"))

При настоящей потоковой обработке уберите list и выполняйте действие непосредственно внутри for. Если каждый блок дописывается в общий bytes через плюс, накопление снова создаст большой результат и лишние копирования. Для хеша вызывайте update, для копирования — write, для счётчика увеличивайте только целое число. Ленивый источник полезен, когда потребитель также не удерживает весь вход.

Кому принадлежит файл

Стоп-значение зависит от интерфейса. Текстовый файл возвращает пустую строку, бинарный — пустые bytes. Если ошибочно сравнивать bytes с "", конец не будет найден по этому правилу. Отдельно проверьте пустой вход: он должен завершиться без единой обработки блока. Для самодельной функции sentinel не должен быть допустимым рабочим результатом, иначе обход остановится раньше времени.

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

Владельцем ресурса в примере остаётся внешний with. Создание итератора не передаёт ему обязанность закрыть файл. При раннем break выход из with всё равно закроет source. Если вы возвращаете ленивый обход из функции, которая сама открыла файл, срок жизни ресурса придётся отразить в интерфейсе. Этот вопрос отдельно разобран в статье о break и закрытии генератора.

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

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

Чтобы свободно собирать ленивые конвейеры, нужно понимать протокол итерации и момент получения следующего элемента. В курсе «Python для профессионалов» есть итераторы, генераторы и продвинутые средства стандартной библиотеки. Откройте программу и продолжите от чтения порций к собственному источнику данных.

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

  • Python для профессионалов ↗

    Python для профессионалов — курс для людей, стремящихся углубить свои знания языка. Вы изучите итераторы, генераторы, декораторы, современные возможности Python (3.8+), освоите метапрограммирование, асинхронность, внутреннее устройство интерпретатора, инструменты оптимизации и профилирования, а также получите уверенность в использовании продвинутых возможностей стандартной библиотеки.