Як я повинен читати файл по черзі в 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

це кращий метод йде вперед?

[ред.] Я розумію, що з оператором із забезпеченням забезпечує закриття файлу ... але чому це не включено в протокол ітератора для файлових об'єктів?


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 ненадовго фліртував з так званим "ледачим 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.


1
+1, тому що це пояснює "коли" в моєму коментарі до оп ;-)
azhrei

навіть при почерговій реалізації, обробник за допомогою лише створює проблеми програмам, які відкривають сотні файлів за дуже швидкі послідовності. Більшість програм можуть вирішити проблему безвісного посилання на файл. Якщо ви не відключите його, врешті-решт GC запустить колись і очистить файл файлу. withвсе-таки дає вам душевний спокій, тому це все-таки найкраща практика.
Лежи Райан

1
@DietrichEpp: можливо, "звисаючий посилання на файл" не був правильним словом, я дійсно мав на увазі ручки файлів, які вже не доступні, але ще не закриті. У будь-якому випадку, GC закриє обробку файлу, коли він збирає файл-об'єкт, тому доки у вас немає додаткових посилань на файл-об'єкт і ви не відключите GC і не відкриєте багато файлів швидко послідовно, ви навряд чи отримаєте "занадто багато файлів відкритих" через закриття файлу.
Лежати Райан

1
Так, саме це я маю на увазі під "якщо ваш код відкриває файли швидше, ніж збирач сміття викликає фіналізатори на осиротілих файлових ручках".
Дітріх Епп

1
Більшою причиною для використання є те, що якщо ви не закриєте файл, він не обов’язково буде написаний негайно.
Сурма

20

так,

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

це шлях.

Це не більш багатослівний. Це більш безпечно.


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()

5
Це насправді не відповідає на питання
Thayne
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.