Lässt das Lesen einer gesamten Datei das Dateihandle offen?

372

Wenn Sie eine ganze Datei mit lesen, content = open('Path/to/file', 'r').read()bleibt das Dateihandle offen, bis das Skript beendet wird? Gibt es eine präzisere Methode zum Lesen einer ganzen Datei?

tMC
quelle

Antworten:

585

Die Antwort auf diese Frage hängt etwas von der jeweiligen Python-Implementierung ab.

Um zu verstehen, worum es geht, achten Sie besonders auf das eigentliche fileObjekt. In Ihrem Code wird dieses Objekt nur einmal in einem Ausdruck erwähnt und kann unmittelbar nach der read()Rückkehr des Aufrufs nicht mehr aufgerufen werden.

Dies bedeutet, dass das Dateiobjekt Müll ist. Die einzige verbleibende Frage lautet: "Wann sammelt der Garbage Collector das Dateiobjekt?".

In CPython, das einen Referenzzähler verwendet, wird diese Art von Müll sofort bemerkt und daher sofort gesammelt. Dies gilt im Allgemeinen nicht für andere Python-Implementierungen.

Eine bessere Lösung, um sicherzustellen, dass die Datei geschlossen ist, ist dieses Muster:

with open('Path/to/file', 'r') as content_file:
    content = content_file.read()

Dadurch wird die Datei immer sofort nach dem Ende des Blocks geschlossen. auch wenn eine Ausnahme auftritt.

Bearbeiten: Um einen genaueren Punkt darauf zu setzen:

Abgesehen davon file.__exit__(), dass dies in einer withKontextmanagereinstellung "automatisch" aufgerufen wird , erfolgt die einzige andere Möglichkeit, file.close()die automatisch aufgerufen wird (dh nicht explizit selbst aufruft), über file.__del__(). Dies führt uns zu der Frage, wann __del__()angerufen wird.

Ein korrekt geschriebenes Programm kann nicht davon ausgehen, dass Finalizer jemals vor dem Beenden des Programms ausgeführt werden.

- https://devblogs.microsoft.com/oldnewthing/20100809-00/?p=13203

Bestimmtes:

Objekte werden niemals explizit zerstört. Wenn sie jedoch nicht mehr erreichbar sind, können sie durch Müll gesammelt werden. Eine Implementierung kann die Speicherbereinigung verschieben oder ganz weglassen ist eine Frage der Implementierungsqualität, wie die Speicherbereinigung implementiert wird, solange keine Objekte erfasst werden, die noch erreichbar sind.

[...]

CPython verwendet derzeit ein Referenzzählschema mit (optionaler) verzögerter Erkennung von zyklisch verknüpftem Müll, bei dem die meisten Objekte gesammelt werden, sobald sie nicht mehr erreichbar sind. Es wird jedoch nicht garantiert, dass Müll mit Zirkelreferenzen gesammelt wird.

- https://docs.python.org/3.5/reference/datamodel.html#objects-values-and-types

(Hervorhebung von mir)

Wie es jedoch nahelegt, können andere Implementierungen ein anderes Verhalten aufweisen. Als Beispiel PyPy hat 6 verschiedene Garbage - Collection - Implementierungen !

SingleNegationElimination
quelle
24
Für eine Weile gab es keine wirklich anderen Python-Implementierungen; Sich auf Implementierungsdetails zu verlassen, ist jedoch nicht wirklich Pythonic.
Karl Knechtel
Ist es noch implementierungsspezifisch oder wurde es bereits standardisiert? __exit__()In solchen Fällen nicht anzurufen, klingt nach einem Konstruktionsfehler.
rr-
2
@jgmjgm Es ist genau wegen dieser 3 Probleme, GC ist unvorhersehbar, try/ finallyfummelig und die sehr häufige Nützlichkeit von Bereinigungshandlern, die withlöst. Der Unterschied zwischen "explizit schließen" und "verwalten mit with" besteht darin, dass der Exit-Handler auch dann aufgerufen wird, wenn eine Ausnahme ausgelöst wird. Sie könnten das close()in eine finallyKlausel witheinfügen , aber das unterscheidet sich nicht wesentlich von der Verwendung , etwas unordentlicher (3 zusätzliche Zeilen anstelle von 1) und etwas schwieriger, genau das Richtige zu finden.
SingleNegationElimination
1
Was ich nicht verstehe, ist, warum 'mit' mehr zuverlässig wäre, da es auch nicht explizit ist. Liegt es daran, dass die Spezifikation besagt, dass sie dies tun muss, dass sie immer so implementiert wird?
jgmjgm
3
@jgmjgm es ist zuverlässiger, weil with foo() as f: [...]es im Grunde das gleiche ist wie f = foo(), f.__enter__()[...] und f.__exit__() mit Ausnahmen behandelt , so dass __exit__das immer aufgerufen wird. Die Datei wird also immer geschlossen.
Neingeist
104

Sie können pathlib verwenden .

Für Python 3.5 und höher:

from pathlib import Path
contents = Path(file_path).read_text()

Verwenden Sie für ältere Versionen von Python pathlib2 :

$ pip install pathlib2

Dann:

from pathlib2 import Path
contents = Path(file_path).read_text()

Dies ist die eigentliche read_text Implementierung :

def read_text(self, encoding=None, errors=None):
    """
    Open the file in text mode, read it, and close the file.
    """
    with self.open(mode='r', encoding=encoding, errors=errors) as f:
        return f.read()
Eyal Levin
quelle
2

Nun, wenn Sie die Datei Zeile für Zeile lesen müssen, um mit jeder Zeile zu arbeiten, können Sie verwenden

with open('Path/to/file', 'r') as f:
    s = f.readline()
    while s:
        # do whatever you want to
        s = f.readline()

Oder noch besser:

with open('Path/to/file') as f:
    for line in f:
        # do whatever you want to
Kirill
quelle
0

Anstatt den Dateiinhalt als einzelne Zeichenfolge abzurufen, kann es nützlich sein, den Inhalt als Liste aller Zeilen zu speichern, die die Datei enthält :

with open('Path/to/file', 'r') as content_file:
    content_list = content_file.read().strip().split("\n")

Wie zu sehen ist, müssen die verketteten Methoden .strip().split("\n")zur Hauptantwort in diesem Thread hinzugefügt werden .

Hier, .strip() nur Leerzeichen und Zeilenumbrüche an den Enden der gesamten Dateizeichenfolge entfernt und .split("\n")die eigentliche Liste erstellt, indem die gesamte Dateizeichenfolge an jedem Zeilenumbruchzeichen aufgeteilt wird \ n .

Darüber hinaus kann auf diese Weise der gesamte Dateiinhalt in einer Variablen gespeichert werden, was in einigen Fällen erwünscht sein kann, anstatt die Datei zeilenweise zu durchlaufen, wie in dieser vorherigen Antwort ausgeführt .

Andreas L.
quelle