Wie soll ich eine Datei Zeile für Zeile in Python lesen?

137

In prähistorischen Zeiten (Python 1.4) haben wir:

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

Nach Python 2.1 haben wir Folgendes getan:

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

bevor wir das praktische Iterator-Protokoll in Python 2.3 erhielten und Folgendes tun konnten:

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

Ich habe einige Beispiele mit der ausführlicheren gesehen:

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

Ist dies die bevorzugte Methode für die Zukunft?

[Bearbeiten] Ich verstehe, dass die with-Anweisung das Schließen der Datei sicherstellt ... aber warum ist das nicht im Iteratorprotokoll für Dateiobjekte enthalten?

thebjorn
quelle
4
Imho, der letzte Vorschlag ist nicht ausführlicher als der vorherige. Es macht einfach mehr Arbeit (stellt sicher, dass die Datei geschlossen wird, wenn Sie fertig sind).
Azhrei
1
@azhrei es ist eine Zeile mehr, also objektiv ist es ausführlicher.
Thebjorn
7
Ich verstehe, was Sie sagen, aber ich sage nur, dass Sie Äpfel mit Äpfeln vergleichen. Der vorletzte Vorschlag in Ihrem Beitrag benötigt ebenfalls Code für die Ausnahmebehandlung, damit er mit der letzten Option übereinstimmt. In der Praxis ist es also ausführlicher. Ich denke, es hängt vom Kontext ab, welche der beiden letzten Optionen wirklich am besten ist.
Azhrei

Antworten:

227

Es gibt genau einen Grund, warum Folgendes bevorzugt wird:

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

Wir alle sind verwöhnt von CPythons relativ deterministischem Referenzzählschema für die Speicherbereinigung. Andere hypothetische Implementierungen von Python schließen die Datei nicht unbedingt "schnell genug" ohne den withBlock, wenn sie ein anderes Schema verwenden, um Speicher zurückzugewinnen.

In einer solchen Implementierung wird vom Betriebssystem möglicherweise der Fehler "Zu viele Dateien öffnen" angezeigt, wenn Ihr Code Dateien schneller öffnet, als der Garbage Collector Finalizer für verwaiste Dateihandles aufruft. Die übliche Problemumgehung besteht darin, den GC sofort auszulösen. Dies ist jedoch ein böser Hack und muss von jeder Funktion ausgeführt werden, bei der der Fehler auftreten kann, einschließlich derjenigen in Bibliotheken. Was ein Alptraum.

Oder Sie könnten einfach den withBlock verwenden.

Bonus-Frage

(Hören Sie jetzt auf zu lesen, wenn Sie nur an den objektiven Aspekten der Frage interessiert sind.)

Warum ist das nicht im Iteratorprotokoll für Dateiobjekte enthalten?

Dies ist eine subjektive Frage zum API-Design, daher habe ich eine subjektive Antwort in zwei Teilen.

Auf der Darmebene fühlt sich dies falsch an, da das Iterator-Protokoll zwei separate Dinge ausführt - über Zeilen iterieren und das Dateihandle schließen - und es oft eine schlechte Idee ist, eine einfach aussehende Funktion zwei Aktionen ausführen zu lassen. In diesem Fall fühlt es sich besonders schlecht an, da sich Iteratoren quasi funktional und wertbasiert auf den Inhalt einer Datei beziehen, die Verwaltung von Dateihandles jedoch eine völlig separate Aufgabe ist. Beide unsichtbar zu einer Aktion zusammenzufassen, ist für Menschen, die den Code lesen, überraschend und erschwert es, über das Programmverhalten nachzudenken.

Andere Sprachen sind im Wesentlichen zu dem gleichen Schluss gekommen. Haskell flirtete kurz mit dem sogenannten "Lazy IO", mit dem Sie eine Datei durchlaufen und automatisch schließen können, wenn Sie am Ende des Streams angelangt sind. Es wird jedoch fast allgemein davon abgeraten, Lazy IO in Haskell und Haskell zu verwenden Benutzer sind größtenteils zu einer expliziteren Ressourcenverwaltung wie Conduit übergegangen, die sich eher wie der withBlock in Python verhält .

Auf technischer Ebene gibt es einige Dinge, die Sie möglicherweise mit einem Dateihandle in Python tun möchten, die nicht so gut funktionieren würden, wenn die Iteration das Dateihandle schließen würde. Angenommen, ich muss die Datei zweimal durchlaufen:

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

Obwohl dies ein weniger häufiger Anwendungsfall ist, bedenken Sie die Tatsache, dass ich möglicherweise gerade die drei Codezeilen unten zu einer vorhandenen Codebasis hinzugefügt habe, die ursprünglich die oberen drei Zeilen hatte. Wenn die Datei durch Iteration geschlossen würde, wäre ich dazu nicht in der Lage. Wenn Sie also Iteration und Ressourcenverwaltung getrennt halten, können Sie einfacher Codeblöcke in einem größeren, funktionierenden Python-Programm zusammenstellen.

Die Kompositionsfähigkeit ist eines der wichtigsten Usability-Merkmale einer Sprache oder API.

Dietrich Epp
quelle
1
+1 weil es das "Wann" in meinem Kommentar zum Op erklärt ;-)
Azhrei
Selbst bei einer alternativen Implementierung verursacht der with-Handler nur Probleme für Programme, die Hunderte von Dateien in sehr schneller Folge öffnen. Die meisten Programme kommen ohne Probleme mit der baumelnden Dateireferenz aus. Wenn Sie es nicht deaktivieren, wird der GC irgendwann einschalten und das Dateihandle aufräumen. withgibt Ihnen jedoch die Gewissheit, dass es immer noch eine bewährte Methode ist.
Lie Ryan
1
@DietrichEpp: Vielleicht waren "baumelnde Dateireferenzen" nicht die richtigen Wörter, ich meinte wirklich Dateihandles, die nicht mehr zugänglich, aber noch nicht geschlossen waren. In jedem Fall schließt der GC das Dateihandle, wenn er das Dateiobjekt sammelt. Solange Sie keine zusätzlichen Verweise auf das Dateiobjekt haben und den GC nicht deaktivieren und nicht viele Dateien schnell öffnen Nacheinander ist es unwahrscheinlich, dass "zu viele Dateien geöffnet" werden, da die Datei nicht geschlossen wird.
Lie Ryan
1
Ja, genau das meine ich mit "Wenn Ihr Code Dateien schneller öffnet, als der Garbage Collector Finalizer für verwaiste Dateihandles aufruft".
Dietrich Epp
1
Der größere Grund für die Verwendung ist, dass die Datei nicht unbedingt sofort geschrieben wird, wenn Sie sie nicht schließen.
Antimon
20

Ja,

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

ist der Weg zu gehen.

Es ist nicht ausführlicher. Es ist sicherer.

Eumiro
quelle
5

Wenn Sie durch die zusätzliche Zeile ausgeschaltet sind, können Sie eine Wrapper-Funktion wie folgt verwenden:

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

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

In Python 3.3 yield fromwürde die Anweisung dies noch kürzer machen:

def with_iter(iterable):
    with iterable as iter:
        yield from iter
Lie Ryan
quelle
2
Rufen Sie die Funktion xreadlines .. auf und fügen Sie sie in eine Datei mit dem Namen xreadlines.py ein. Wir kehren zur Python 2.1-Syntax zurück :-)
thebjorn
@thebjorn: Vielleicht, aber das von Ihnen zitierte Python 2.1-Beispiel war in alternativen Implementierungen nicht vor nicht geschlossenem Datei-Handler sicher. Ein Python 2.1-Dateilesen, der vor nicht geschlossenem Dateihandler sicher ist, würde mindestens 5 Zeilen benötigen.
Lie Ryan
-2
f = open('test.txt','r')
for line in f.xreadlines():
    print line
f.close()
Rekaut
quelle
5
Dies beantwortet die Frage nicht wirklich
Thayne