Czy odczyt całego pliku pozostawia otwarty uchwyt pliku?

372

Jeśli czytasz cały plik, content = open('Path/to/file', 'r').read()czy uchwyt pliku pozostaje otwarty, dopóki skrypt nie zostanie zamknięty? Czy istnieje bardziej zwięzła metoda odczytu całego pliku?

tMC
źródło

Odpowiedzi:

585

Odpowiedź na to pytanie zależy w pewnym stopniu od konkretnej implementacji języka Python.

Aby zrozumieć, o co w tym wszystkim chodzi, zwróć szczególną uwagę na rzeczywisty fileprzedmiot. W kodzie ten obiekt jest wymieniony tylko raz, w wyrażeniu, i staje się niedostępny natychmiast po read()powrocie wywołania.

Oznacza to, że obiekt pliku jest śmieci. Pozostaje tylko pytanie: „Kiedy śmieciarz zbierze obiekt pliku?”.

w CPython, który korzysta z licznika referencyjnego, ten rodzaj śmieci jest natychmiast zauważany, a więc zostanie natychmiast zebrany. Zasadniczo nie dotyczy to innych implementacji języka Python.

Lepszym rozwiązaniem, aby upewnić się, że plik jest zamknięty, jest następujący wzorzec:

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

co zawsze zamyka plik natychmiast po zakończeniu bloku; nawet jeśli wystąpi wyjątek.

Edycja: Aby dodać drobniejszy punkt:

Poza tym file.__exit__(), które jest „automatycznie” wywoływane w withustawieniach menedżera kontekstu, jedynym innym sposobem, który file.close()jest wywoływany automatycznie (to znaczy inaczej niż jawne wywoływanie go osobiście) jest poprzez file.__del__(). To prowadzi nas do pytania, kiedy __del__()zostanie wywołany?

Prawidłowo napisany program nie może zakładać, że finalizatory będą działać w dowolnym momencie przed zakończeniem programu.

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

W szczególności:

Obiekty nigdy nie są jawnie niszczone; jednak gdy staną się nieosiągalne, mogą zostać zebrane w śmieci. Implementacja może odroczyć odrzucanie elementów bezużytecznych lub całkowicie je pominąć - kwestią jakości implementacji jest sposób, w jaki jest ono stosowane, o ile nie są gromadzone obiekty, które są nadal dostępne.

[...]

CPython obecnie używa schematu zliczania referencji z (opcjonalnym) opóźnionym wykrywaniem cyklicznie powiązanych śmieci, które zbierają większość obiektów, gdy tylko stają się one nieosiągalne, ale nie ma gwarancji, że zbierają śmieci zawierające cykliczne odwołania.

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

(Moje podkreślenie)

ale jak sugeruje, inne implementacje mogą mieć inne zachowanie. Na przykład PyPy ma 6 różnych implementacji odśmiecania !

SingleNegationElimination
źródło
24
Przez pewien czas nie było tak naprawdę innych implementacji Pythona; ale poleganie na szczegółach implementacji nie jest tak naprawdę Pythonic.
Karl Knechtel,
Czy nadal jest specyficzny dla implementacji, czy był już standaryzowany? Brak dzwonienia __exit__()w takich przypadkach brzmi jak wada projektowa.
rr-
2
@jgmjgm To właśnie z powodu tych 3 problemów, GC jest nieprzewidywalny, try/ finallyjest niespokojny i bardzo często bezużyteczny dla programów czyszczących, które withrozwiązują. Różnica między „jawnym zamykaniem” a „zarządzaniem za pomocą with” polega na tym, że program obsługi wyjścia jest wywoływany, nawet jeśli zostanie zgłoszony wyjątek. Można umieścić close()w finallyklauzuli, ale nie różni się zbytnio od używania withzamiast, trochę Messiera (3 dodatkowe linie zamiast 1), a trochę trudniej dostać tylko w prawo.
SingleNegationElimination
1
Nie rozumiem, dlaczego „with” byłby bardziej niezawodny, ponieważ nie jest również jednoznaczny. Czy to dlatego, że specyfikacja mówi, że musi to zrobić, że zawsze jest tak zaimplementowana?
jgmjgm
3
@jgmjgm to bardziej wiarygodne, ponieważ with foo() as f: [...]jest w zasadzie taka sama jak f = foo(), f.__enter__()[...] oraz f.__exit__() z wyjątkami obsługiwane tak, że __exit__zawsze jest wywoływana. Plik zawsze się zamyka.
neingeist
104

Możesz użyć pathlib .

W przypadku języka Python 3.5 i nowszych:

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

W przypadku starszych wersji Pythona użyj pathlib2 :

$ pip install pathlib2

Następnie:

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

To jest faktyczna read_text implementacja :

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
źródło
2

Cóż, jeśli musisz czytać plik linia po linii, aby pracować z każdą linią, możesz użyć

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

Lub jeszcze lepszy sposób:

with open('Path/to/file') as f:
    for line in f:
        # do whatever you want to
Cyryl
źródło
0

Zamiast pobierać zawartość pliku jako pojedynczy ciąg, przydatne może być przechowywanie zawartości jako listy wszystkich wierszy pliku :

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

Jak widać, należy dodać połączone metody .strip().split("\n")do głównej odpowiedzi w tym wątku .

Tutaj .strip()po prostu usuwa znaki białych znaków i znaków nowej linii na końcach całego ciągu pliku i .split("\n")tworzy rzeczywistą listę poprzez podział całego łańcucha pliku przy każdym znaku nowego wiersza \ n .

Ponadto w ten sposób cała zawartość pliku może być przechowywana w zmiennej, co może być pożądane w niektórych przypadkach, zamiast zapętlania pliku po linii, jak wskazano w poprzedniej odpowiedzi .

Andreas L.
źródło