Jak czytać plik wiersz po wierszu w Pythonie?

137

W czasach prehistorycznych (Python 1.4) zrobiliśmy:

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

po Pythonie 2.1 zrobiliśmy:

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

zanim otrzymaliśmy wygodny protokół iteratora w Pythonie 2.3 i mogliśmy zrobić:

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

Widziałem kilka przykładów z bardziej szczegółowym:

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

czy jest to preferowana metoda na przyszłość?

[edytuj] Rozumiem, że instrukcja with zapewnia zamknięcie pliku ... ale dlaczego nie jest to zawarte w protokole iteratora dla obiektów plikowych?

thebjorn
źródło
4
imho, ostatnia sugestia nie jest bardziej szczegółowa niż poprzednia. Po prostu wykonuje więcej pracy (zapewnia zamknięcie pliku, gdy skończysz).
azhrei
1
@azhrei to jeden wiersz więcej, więc obiektywnie jest bardziej szczegółowy.
thebjorn
7
Rozumiem, co mówisz, ale mówię tylko o porównywaniu jabłek z jabłkami, przedostatnia sugestia w twoim poście wymaga również kodu obsługi wyjątków, aby dopasować to, co robi ostatnia opcja. W praktyce jest to bardziej szczegółowe. Myślę, że zależy to od kontekstu, która z dwóch ostatnich opcji jest najlepsza.
azhrei

Odpowiedzi:

227

Jest dokładnie jeden powód, dla którego preferowane jest to:

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

Wszyscy jesteśmy rozpieszczani przez stosunkowo deterministyczny schemat liczenia odniesień w CPythonie do czyszczenia pamięci. Inne, hipotetyczne implementacje Pythona niekoniecznie będą musiały zamknąć plik „wystarczająco szybko” bez withbloku, jeśli używają innego schematu odzyskiwania pamięci.

W takiej implementacji może pojawić się błąd „zbyt wiele otwartych plików” z systemu operacyjnego, jeśli kod otwiera pliki szybciej niż moduł odśmiecania wywołań finalizatorów na uchwytach osieroconych plików. Zwykłym obejściem jest natychmiastowe wyzwolenie GC, ale jest to paskudny hack i musi to być wykonane przez każdą funkcję, która może napotkać błąd, w tym te w bibliotekach. Co za koszmar.

Lub możesz po prostu użyć withbloku.

Pytanie dodatkowe

(Przestań czytać teraz, jeśli interesują Cię tylko obiektywne aspekty pytania).

Dlaczego nie jest to uwzględnione w protokole iteratora dla obiektów plików?

To jest subiektywne pytanie dotyczące projektowania API, więc mam subiektywną odpowiedź w dwóch częściach.

Na poziomie intuicji wydaje się to złe, ponieważ sprawia, że ​​protokół iteratora wykonuje dwie oddzielne rzeczy - iteruje po liniach i zamyka uchwyt pliku - i często jest złym pomysłem, aby prosta funkcja wykonywała dwie czynności. W tym przypadku jest to szczególnie złe, ponieważ iteratory odnoszą się w quasi-funkcjonalny, oparty na wartościach sposób do zawartości pliku, ale zarządzanie uchwytami plików to zupełnie osobne zadanie. Zgniecenie obu, niewidocznie, w jedną akcję, jest zaskakujące dla ludzi, którzy czytają kod i utrudnia wnioskowanie o zachowaniu programu.

Inne języki zasadniczo doszły do ​​tego samego wniosku. Haskell krótko flirtował z tak zwanym „leniwym IO”, który pozwala na iterację pliku i automatyczne zamknięcie go, gdy dojdziesz do końca strumienia, ale obecnie prawie zawsze odradza się używanie leniwego IO w Haskell i Haskell użytkownicy przeważnie przeszli do bardziej jawnego zarządzania zasobami, takiego jak Conduit, który zachowuje się bardziej jak withblok w Pythonie.

Na poziomie technicznym jest kilka rzeczy, które możesz chcieć zrobić z uchwytem pliku w Pythonie, który nie działałby tak dobrze, gdyby iteracja zamknęła uchwyt pliku. Na przykład załóżmy, że muszę dwukrotnie powtórzyć ten plik:

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

Chociaż jest to mniej powszechny przypadek użycia, weź pod uwagę fakt, że mogłem właśnie dodać trzy wiersze kodu na dole do istniejącej bazy kodu, która pierwotnie miała trzy górne wiersze. Gdyby iteracja zamknęła plik, nie byłbym w stanie tego zrobić. Tak więc oddzielenie iteracji i zarządzania zasobami ułatwia komponowanie fragmentów kodu w większy, działający program w języku Python.

Kompatybilność jest jedną z najważniejszych cech użyteczności języka lub API.

Dietrich Epp
źródło
1
+1, ponieważ wyjaśnia „kiedy” w moim komentarzu do operacji ;-)
azhrei.
nawet z alternatywną implementacją, program obsługi with spowoduje problemy tylko dla programów, które otwierają setki plików w bardzo krótkich odstępach czasu. Większość programów radzi sobie bez problemu z wiszącym odniesieniem do pliku. O ile go nie wyłączysz, w końcu GC włączy się kiedyś i wyczyści uchwyt pliku. withzapewnia jednak spokój ducha, więc nadal jest to najlepsza praktyka.
Lie Ryan,
1
@DietrichEpp: być może „wiszące odniesienie do pliku” nie było właściwymi słowami, naprawdę chodziło mi o uchwyty plików, które nie były już dostępne, ale jeszcze nie zostały zamknięte. W każdym razie GC zamknie uchwyt pliku, gdy zbierze obiekt pliku, dlatego tak długo, jak nie masz dodatkowych odniesień do obiektu pliku i nie wyłączysz GC i nie otwierasz wielu plików w szybkim tempie sukcesji, jest mało prawdopodobne, aby „otworzyło się zbyt wiele plików”, ponieważ plik nie został zamknięty.
Lie Ryan,
1
Tak, dokładnie to mam na myśli mówiąc „jeśli twój kod otwiera pliki szybciej niż garbage collector wywołuje finalizatory na osieroconych uchwytach plików”.
Dietrich Epp
1
Większym powodem używania with jest to, że jeśli nie zamkniesz pliku, niekoniecznie zostanie on natychmiast zapisany.
Antymon
20

Tak,

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

jest droga do zrobienia.

Nie jest bardziej szczegółowe. To jest bezpieczniejsze.

eumiro
źródło
5

jeśli jesteś wyłączony przez dodatkową linię, możesz użyć funkcji opakowującej, takiej jak ta:

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

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

w Pythonie 3.3 yield frominstrukcja sprawiłaby, że jest to jeszcze krótsze:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
Lie Ryan
źródło
2
wywołaj funkcję xreadlines .. i umieść ją w pliku o nazwie xreadlines.py i wracamy do składni Pythona 2.1 :-)
thebjorn
@thebjorn: być może, ale cytowany przykład Pythona 2.1 nie był bezpieczny przed niezamkniętym programem obsługi plików w alternatywnych implementacjach. Odczyt pliku Pythona 2.1, który jest bezpieczny przed niezamkniętym programem obsługi plików, zajmie co najmniej 5 linii.
Lie Ryan,
-1
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
źródło
5
To tak naprawdę nie odpowiada na pytanie
Thayne