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

137

В доисторические времена (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

это предпочтительный метод продвижения вперед?

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

thebjorn
источник
4
Имхо, последнее предложение не более многословно, чем предыдущее. Это просто делает больше работы (гарантирует, что файл будет закрыт, когда вы закончите).
Ажрей
1
@azhrei это на одну строчку больше, поэтому объективно более многословно.
Thebjorn
7
Я понимаю, что вы говорите, но я просто говорю, сравнивая яблоки с яблоками, второе последнее предложение в вашем посте также требует кода обработки исключений, чтобы соответствовать тому, что делает последний вариант. Так что на практике это более многословно. Я думаю, это зависит от контекста, какой из двух последних вариантов является лучшим, на самом деле.
Ажрей

Ответы:

227

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

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

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

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

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

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

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

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

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

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

Другие языки по сути пришли к такому же выводу. Haskell кратко заигрывает с так называемым «ленивым вводом-выводом», который позволяет вам перебирать файл и автоматически закрывать его, когда вы достигаете конца потока, но в наши дни почти повсеместно не рекомендуется использовать отложенный ввод-вывод в 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.

Дитрих Эпп
источник
1
+1, потому что это объясняет «когда» в моем комментарии к оп ;-)
azhrei
даже при альтернативной реализации обработчик with вызовет проблемы только у программ, открывающих сотни файлов очень быстро. Большинство программ могут обойтись без висящей ссылки на файл. Если вы не отключите его, в конечном итоге GC через некоторое время включится и очистит дескриптор файла. withдействительно дает вам душевное спокойствие, так что это все еще лучшая практика.
Ли Райан
1
@DietrichEpp: возможно, «висящая ссылка на файл» - не те слова, я действительно имел в виду файловые дескрипторы, которые больше не были доступны, но еще не закрыты. В любом случае, GC закроет дескриптор файла, когда он собирает объект файла, поэтому, если у вас нет дополнительных ссылок на объект файла, и вы не отключаете GC, и вы не открываете много файлов быстро подряд, вы вряд ли получите «слишком много открытых файлов» из-за того, что не закрыли файл.
Ли Райан
1
Да, это именно то, что я подразумеваю под «если ваш код открывает файлы быстрее, чем сборщик мусора вызывает финализаторы для потерянных файловых дескрипторов».
Дитрих Эпп
1
Основная причина использования этого метода заключается в том, что если вы не закроете файл, он не обязательно будет записан немедленно.
Сурьма
20

Да,

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

это путь

Это не более многословно. Это более безопасно.

eumiro
источник
5

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

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
Ли Райан
источник
2
вызовите функцию xreadlines .. и поместите ее в файл с именем xreadlines.py, и мы вернемся к синтаксису Python 2.1 :-)
thebjorn
@thebjorn: возможно, но приведенный вами пример Python 2.1 не был защищен от незамеченного обработчика файлов в альтернативных реализациях. Чтение файла Python 2.1, которое защищено от обработчика незакрытого файла, займет не менее 5 строк.
Ли Райан
-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
источник
5
Это на самом деле не отвечает на вопрос
Тейн