Как читать файл по строкам в Python?

В доисторические времена (Python 1.4) мы сделали:

fp = open('filename.txt')
while 1:
    line = fp.readline()
    if not line:
        break
    print line

после Python 2.1 мы сделали:

for line in open('filename.txt').xreadlines():
    print line

прежде чем мы получим удобный протокол итератора в Python 2.3 и можем сделать:

for line in open('filename.txt'):
    print line

Я видел несколько примеров, используя более подробные:

with open('filename.txt') as fp:
    for line in fp:
        print line

- это предпочтительный метод, идущий вперед?

[edit] Я получаю, что оператор with обеспечивает закрытие файла... но почему это не входит в протокол итератора для файловых объектов?

Ответ 1

Существует только одна причина, по которой предпочтительнее следующее:

with open('filename.txt') as fp:
    for line in fp:
        print line

Мы все испорчены CPython относительно детерминированной схемой подсчета ссылок для сбора мусора. Другие гипотетические реализации Python не обязательно будут закрывать файл "достаточно быстро" без блока with, если они используют какую-то другую схему для восстановления памяти.

В такой реализации вы можете получить сообщение об ошибке "слишком много файлов" из ОС, если ваш код открывает файлы быстрее, чем сборщики мусора собирают финализаторы в обработанных сиротских файлах. Обычное обходное решение - немедленно запустить GC, но это неприятный взлом, и это нужно сделать с помощью каждой функции, которая может столкнуться с ошибкой, в том числе в библиотеках. Какой кошмар.

Или вы можете просто использовать блок with.

Бонусный вопрос

(перестаньте читать, если интересуетесь только объективными аспектами вопроса.)

Почему это не включено в протокол итератора для файловых объектов?

Это субъективный вопрос об дизайне API, поэтому у меня есть субъективный ответ в двух частях.

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

Другие языки по существу пришли к одному и тому же выводу. Haskell ненадолго флиртовал с так называемым "ленивым IO", который позволяет вам перебирать файл и автоматически закрывать его, когда вы добираетесь до конца потока, но почти повсеместно не рекомендуется использовать ленивый IO в Haskell в эти дни, а Haskell пользователи в основном перешли к более явному управлению ресурсами, например, Conduit, который больше похож на блок with на Python.

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

with open('filename.txt') as fp:
    for line in fp:
        ...
    fp.seek(0)
    for line in fp:
        ...

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

Совместимость - одна из самых важных функций удобства использования языка или API.

Ответ 2

Да,

with open('filename.txt') as fp:
    for line in fp:
        print line

- путь.

Это не более подробный. Это безопаснее.

Ответ 3

если вы отключены дополнительной строкой, вы можете использовать такую ​​обертку:

def with_iter(iterable):
    with iterable as iter:
        for item in iter:
            yield item

for line in with_iter(open('...')):
    ...

в Python 3.3, оператор yield from сделает это еще короче:

def with_iter(iterable):
    with iterable as iter:
        yield from iter

Ответ 4

f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()