¿Leer un archivo completo deja abierto el identificador de archivo?

372

Si lee un archivo completo, ¿ content = open('Path/to/file', 'r').read()está abierto el identificador de archivo hasta que salga el script? ¿Existe un método más conciso para leer un archivo completo?

tMC
fuente

Respuestas:

585

La respuesta a esa pregunta depende en cierta medida de la implementación particular de Python.

Para comprender de qué se trata todo esto, preste especial atención al fileobjeto real . En su código, ese objeto se menciona solo una vez, en una expresión, y se vuelve inaccesible inmediatamente después de que read()vuelve la llamada.

Esto significa que el objeto del archivo es basura. La única pregunta que queda es "¿Cuándo recogerá el recolector de basura el objeto de archivo?".

En CPython, que utiliza un contador de referencia, este tipo de basura se nota inmediatamente, por lo que se recogerá de inmediato. Esto no es generalmente cierto en otras implementaciones de Python.

Una mejor solución, para asegurarse de que el archivo esté cerrado, es este patrón:

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

que siempre cerrará el archivo inmediatamente después de que finalice el bloque; incluso si ocurre una excepción.

Editar: para ponerle un punto más fino:

Aparte de file.__exit__(), que se llama "automáticamente" en una withconfiguración de administrador de contexto, la única otra forma que file.close()se llama automáticamente (es decir, aparte de llamarlo explícitamente usted mismo) es a través de file.__del__(). Esto nos lleva a la pregunta de cuándo __del__()se llama?

Un programa escrito correctamente no puede suponer que los finalizadores se ejecutarán en algún momento antes de la finalización del programa.

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

En particular:

Los objetos nunca se destruyen explícitamente; sin embargo, cuando se vuelven inalcanzables, se pueden recolectar basura. Se permite a una implementación posponer la recolección de basura u omitirla por completo : es una cuestión de calidad de implementación cómo se implementa la recolección de basura, siempre que no se recolecten objetos que aún sean accesibles.

[...]

CPython actualmente utiliza un esquema de conteo de referencias con detección retrasada (opcional) de basura enlazada cíclicamente, que recolecta la mayoría de los objetos tan pronto como se vuelven inalcanzables, pero no se garantiza que recolecte basura que contenga referencias circulares.

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

(El énfasis es mío)

pero como sugiere, otras implementaciones pueden tener otro comportamiento. Como ejemplo, PyPy tiene 6 implementaciones diferentes de recolección de basura .

SingleNegationElimination
fuente
24
Durante un tiempo, no hubo realmente otras implementaciones de Python; pero confiar en los detalles de implementación no es realmente Pythonic.
Karl Knechtel
¿Sigue siendo específico de la implementación o ya estaba estandarizado? No llamar __exit__()en tales casos suena como un defecto de diseño.
rr-
2
@jgmjgm Es precisamente debido a esos 3 problemas, GC es impredecible, try/ finallyes complicado y la muy inútil utilidad de los controladores de limpieza que withresuelve. La diferencia entre "cerrar explícitamente" y "administrar con with" es que se llama al controlador de salida incluso si se produce una excepción. Podría poner el close()en una finallycláusula, pero eso no es muy diferente de usar en su withlugar, un poco más desordenado (3 líneas adicionales en lugar de 1), y un poco más difícil de conseguir.
SingleNegationElimination
1
Lo que no entiendo sobre eso es por qué 'con' sería más confiable ya que tampoco es explícito. ¿Es porque la especificación dice que tiene que hacer que siempre se implementa así?
jgmjgm
3
@jgmjgm es más confiable porque with foo() as f: [...]es básicamente lo mismo que f = foo(), f.__enter__()[...] y f.__exit__() con excepciones manejadas , por lo que __exit__siempre se llama. Entonces el archivo siempre se cierra.
neingeist
104

Puedes usar pathlib .

Para Python 3.5 y superior:

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

Para versiones anteriores de Python, use pathlib2 :

$ pip install pathlib2

Entonces:

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

Esta es la read_text implementación real :

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
fuente
2

Bueno, si tiene que leer el archivo línea por línea para trabajar con cada línea, puede usar

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

O incluso mejor manera:

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

En lugar de recuperar el contenido del archivo como una sola cadena, puede ser útil almacenar el contenido como una lista de todas las líneas que comprende el archivo :

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

Como se puede ver, uno necesita agregar los métodos concatenados .strip().split("\n")a la respuesta principal en este hilo .

Aquí, .strip()solo elimina los espacios en blanco y los caracteres de nueva línea en las terminaciones de toda la cadena del archivo, y .split("\n")produce la lista real dividiendo la cadena completa del archivo en cada carácter de nueva línea \ n .

Además, de esta manera, todo el contenido del archivo puede almacenarse en una variable, lo que puede desearse en algunos casos, en lugar de recorrer el archivo línea por línea como se indicó en esta respuesta anterior .

Andreas L.
fuente