Jak czytać plik wiersz po wierszu w Pythonie?

W czasach przedhistorycznych (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 dostaliś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 użyciem bardziej wyrazistych:

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

Czy jest to preferowana metoda przechodzenia do przodu?

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

Author: thebjorn, 2012-07-19

4 answers

Jest dokładnie jeden powód, dla którego następujące są preferowane:

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

Wszyscy jesteśmy zepsuci przez relatywnie deterministyczny schemat zliczania referencji Cpythona do zbierania śmieci. Inne, hipotetyczne implementacje Pythona niekoniecznie zamkną plik "wystarczająco szybko" bez bloku with, jeśli użyją innego schematu do odzyskania pamięci.

W takiej implementacji może wystąpić błąd "zbyt wiele otwartych plików" z systemu operacyjnego, jeśli kod otwiera pliki szybciej niż garbage collector wywołuje finalizery na osieroconych uchwytach plików. Zwykle obejście polega na natychmiastowym uruchomieniu GC, ale jest to paskudny hack i musi to być zrobione przez każdą funkcję, która może napotkać błąd, w tym te w bibliotekach. Co za koszmar.

Albo możesz użyć with bloku.

Pytanie Bonusowe

(przestań czytać teraz, jeśli jesteś zainteresowany tylko obiektywnymi aspektami pytania.)

Dlaczego nie jest to zawarte w protokół iteracyjny dla obiektów plików?

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

Na poziomie jelita, wydaje się to złe, ponieważ sprawia, że protokół iteratora robi dwie oddzielne rzeczy-iterację po liniach i zamykanie uchwytu pliku-i często jest złym pomysłem, aby Prosta funkcja wykonywała dwie akcje. W tym przypadku jest to szczególnie złe, ponieważ Iteratory odnoszą się w quasi-funkcjonalny, oparty na wartości sposób do zawartość pliku, ale zarządzanie uchwytami plików jest całkowicie oddzielnym zadaniem. Zgniatanie obu, niewidoczne, w jedną akcję, jest zaskakujące dla ludzi, którzy czytają kod i utrudnia rozumowanie o zachowaniu programu.

Inne języki zasadniczo doszły do tego samego wniosku. Haskell na krótko flirtował z tak zwanym "leniwym IO", który pozwala na iterację nad plikiem i automatyczne zamykanie go po dotarciu do końca strumienia, ale jest prawie uniwersalny zniechęcony do korzystania z leniwych IO w Haskell, a użytkownicy Haskell głównie przenieśli się do bardziej wyraźnego zarządzania zasobami, takiego jak Conduit, który zachowuje się bardziej jak blok with w Pythonie.

Na poziomie technicznym, są pewne rzeczy, które możesz chcieć zrobić z uchwytem pliku w Pythonie, które nie będą działać tak dobrze, jeśli iteracja zamknie uchwyt pliku. Na przykład, załóżmy, że muszę wykonać iterację nad plikiem dwa razy:

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 mógł po prostu dodać trzy linie kodu na dole do istniejącej bazy kodu, która pierwotnie miała trzy górne linie. 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 Pythona.

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

 187
Author: Dietrich Epp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-25 06:33:15

Tak,

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

Jest droga do zrobienia.

To nie jest bardziej gadatliwe. Jest bezpieczniejszy.

 18
Author: eumiro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 07:01:35

Jeśli zostaniesz wyłączony przez dodatkową linię, możesz użyć funkcji wrappera w następujący sposób:

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

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

W Pythonie 3.3, twierdzenie yield from czyniłoby to jeszcze krótszym:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
 3
Author: Lie Ryan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-19 08:03:44
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
 -3
Author: Rekaut,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-02-03 13:10:22