¿Por qué es importante cerrar archivos en Python?

En algún momento de su viaje de codificación de Python, aprende que debe usar un administrador de contexto para abrir archivos. Los administradores de contexto de Python facilitan el cierre de sus archivos una vez que haya terminado con ellos:

with open("hello.txt", mode="w") as file:
    file.write("Hello, World!")

LoswithLa instrucción inicia un gestor de contexto. En este ejemplo, el gestor de contexto abre el archivohello.txty gestiona el recurso de archivo siempre que el contexto esté activo. En general, todo el código en el bloque sangría depende de que el objeto de archivo esté abierto. Una vez que el bloque sangría termina o genera una excepción, entonces el archivo se cerrará.

Si no está utilizando un gestor de contexto o está trabajando en un idioma diferente, entonces puede cerrar archivos explícitamente con el administrador de contexto.tryfinallyenfoque:

try:
    file = open("hello.txt", mode="w")
    file.write("Hello, World!")
finally:
    file.close()

Losfinallybloque que cierra el archivo se ejecuta incondicionalmente, si eltrybloque tiene éxito o falla. Si bien esta sintaxis cierra efectivamente el archivo, el gestor de contextos de Python ofrece una sintaxis menos detallada y más intuitiva. Además, es un poco más flexible que simplemente envolver su código contryfinally.

Probablemente ya uses gestores de contexto para administrar archivos, pero ¿alguna vez te has preguntado por qué la mayoría de los tutoriales y cuatro de cada cinco dentistas recomiendan hacer esto? En resumen, ¿por qué es importante cerrar archivos en Python?

En este tutorial, te sumergirás en esa misma pregunta. Primero, aprenderá cómo los manejadores de archivos son un recurso limitado. A continuación, experimentará con las consecuencias de no cerrar sus archivos.

Descarga gratuita: obtenga un capítulo de ejemplo de CPython Internals: Your Guide to the Python 3 Interpreter que le muestra cómo desbloquear el funcionamiento interno del lenguaje Python, compilar el intérprete de Python desde el código fuente y participar en el desarrollo de CPython.

En resumen: los archivos están limitados por el sistema operativo

Python delega las operaciones de archivo al sistema operativo. El sistema operativo es el mediador entre los procesos, como Python, y todos los recursos del sistema, como el disco duro, la RAM y el tiempo de CPU.

Cuando abres un archivo conopen(), usted hace una llamada al sistema operativo para localizar ese archivo en el disco duro y prepararlo para la lectura o escritura. El sistema operativo devolverá un entero sin signo llamado manejador de archivos en Windows y un descriptor de archivos en sistemas similares a UNIX, incluidos Linux y macOS:

Un proceso de Python que realiza una llamada al sistema y obtiene el número entero 10 como el manejador de archivos

Una vez que tenga el número asociado con el archivo, estará listo para hacer operaciones de lectura o escritura. Cada vez que Python quiera leer, escribir o cerrar el archivo, hará otra llamada al sistema, proporcionando el número de identificador de archivo. El objeto de archivo Python tiene un.fileno()método que puede usar para encontrar el manejador de archivos:

>>> with open("test_file.txt", mode="w") as file:
...     file.fileno()
...
4

Los.fileno()El método en el objeto de archivo abierto devolverá el entero usado por el sistema operativo como un descriptor de archivo. Al igual que puede usar un campo ID para obtener un registro de una base de datos, Python proporciona este número al sistema operativo cada vez que lee o escribe desde un archivo.

Los sistemas operativos limitan el número de archivos abiertos que cualquier proceso puede tener. Este número es típicamente de miles. Los sistemas operativos establecen este límite porque si un proceso intenta abrir miles de descriptores de archivo, es probable que algo esté mal con el proceso. A pesar de que miles de archivos pueden parecer mucho, todavía es posible llegar al límite.

Aparte del riesgo de llegar al límite, mantener los archivos abiertos te deja vulnerable a la pérdida de datos. En general, Python y el sistema operativo trabajan duro para protegerte de la pérdida de datos. Pero si su programa, o computadora, falla, es posible que las rutinas habituales no se lleven a cabo y que los archivos abiertos se corrompan.

Nota: Algunas bibliotecas tienen métodos y funciones específicos que parecen abrir archivos sin un administrador de contexto. Por ejemplo, la biblioteca pathlib tiene.write_text(), y los pandas tienenread_csv().

Sin embargo, administran los recursos adecuadamente bajo el capó, por lo que no es necesario usar un administrador de contexto en esos casos. Lo mejor es consultar la documentación de la biblioteca que está utilizando para ver si necesita un gestor de contexto o no.

En resumen, dejar que los administradores de contexto administren sus archivos es una técnica defensiva que es fácil de practicar y mejora su código, por lo que también podría hacerlo. Es como usar un cinturón de seguridad. Es probable que no lo necesite, pero los costos de ir sin pueden ser altos.

En el resto de este tutorial, profundizarás en los límites, las consecuencias y los peligros de no cerrar archivos. En la siguiente sección, explorarás elToo many open fileserror.

¿Qué pasa cuando abres demasiados archivos?

En esta sección, explorará lo que sucede cuando se ejecuta en el límite de archivos. Lo harás probando un fragmento de código que creará una carga de archivos abiertos y provocará un error.OSError.

Nota: Como elOSenOSErrorsugiere que el límite es impuesto por el sistema operativo y no por Python. Sin embargo, el sistema operativo podría, en teoría, lidiar con muchos más descriptores de archivos. Más adelante, aprenderá más sobre por qué el sistema operativo limita los controladores de archivos.

Puede probar el límite de archivos por proceso en su sistema operativo tratando de abrir miles de archivos a la vez. Almacenará los objetos de archivo en una lista para que no se limpien automáticamente. Pero primero, querrás hacer un poco de limpieza para asegurarte de no crear muchos archivos en algún lugar que no los quieras:

$ mkdir file_experiment
$ cd file_experiment

Crear una carpeta donde pueda volcar los archivos y luego navegar a esa carpeta es suficiente. Luego, puede abrir un REPL de Python e intentar crear miles de archivos:

>>> files = [open(f"file-{n}.txt", mode="w") for n in range(10_000)]
Traceback (most recent call last):
    ...
OSError: [Errno 24] Too many open files: 'file-1021.txt'

Este fragmento intenta abrir diez mil archivos y mantenerlos en una lista. El sistema operativo comienza a crear archivos, pero retrocede una vez que ha alcanzado su límite. Si lista los archivos en su directorio recién creado, notará que a pesar de que la comprensión de la lista finalmente falló, el sistema operativo hizo muchos de los archivos, pero no los diez mil que solicitó.

El límite que encuentre variará entre los sistemas operativos y parece más grande por defecto en Windows. Dependiendo del sistema operativo, hay formas de aumentar este límite de archivos por proceso. Sin embargo, debes preguntarte si realmente necesitas hacerlo. Solo hay unos pocos casos de uso legítimos para elegir esta solución.

Un escenario legítimo es para los servidores. Los servidores funcionan con sockets, que se tratan de manera muy similar a los archivos. El sistema operativo realiza un seguimiento de los sockets en la tabla de archivos mediante los controladores de archivos. Un servidor puede necesitar tener muchos sockets abiertos para cada cliente al que se conectan. Además, un servidor puede estar comunicándose con varios clientes. Esta situación puede llevar a que se requieran muchos miles de controladores de archivo.

Curiosamente, a pesar de que ciertas aplicaciones pueden requerir aumentar el límite del sistema operativo para los archivos abiertos, por lo general son estas mismas aplicaciones las que deben ser especialmente diligentes en el cierre de archivos.

Tal vez pienses que no estás en peligro inmediato de llegar al límite. Aun así, sigue leyendo, porque en la siguiente sección, echarás un vistazo más de cerca a algunas de las consecuencias de llegar accidentalmente a ese límite.

¿Cuáles son las consecuencias de la vida real de correr en el límite de archivos?

Si abre archivos y nunca los cierra en Python, es posible que no note ninguna diferencia, especialmente si está trabajando en scripts de un solo archivo o proyectos pequeños. Sin embargo, a medida que los proyectos en los que trabaja crezcan en complejidad, aumentará su exposición a situaciones problemáticas.

Imagina que estás trabajando en un gran equipo en una base de código masiva. Entonces, un día llegas al límite para los archivos abiertos. El kicker es que el mensaje de error para el límite no le dirá dónde está el problema. Será el genéricoOSErrorque viste antes, que solo te diceToo many open files.

Puede tener miles de lugares en su base de código donde abre archivos. Imagínese la búsqueda de lugares donde el código no maneja los archivos correctamente. Imagine que el código pasa objetos de archivo entre funciones, y no puede saber de inmediato si algún objeto de archivo dado finalmente se cierra o no. No es un momento divertido.

Si está interesado, hay formas de explorar los manejadores de archivos abiertos de su sistema. Expanda el siguiente bloque para explorar:

Utilidades para explorar manejadores de archivosMostrar/Ocultar

  • Ventanas
  • Linux + macOS

Instalar el hacker de proceso:

PS> choco install processhacker

Abra la aplicación y haga clic en el botón Buscar controladores o DLL. Marque la casilla de verificación regex y escriba.*para ver todos los manejadores de archivos con la información que los acompaña.

La versión oficial de Microsoft del hacker de procesos es parte de las utilidades Sysinternals, a saber, Process Monitor y Process Explorer.

Es posible que necesite instalarlsof, que es una utilidad de Linux para listar archivos abiertos. Con esta utilidad, puede obtener información y contar cuántos archivos abiertos hay:

$ lsof | head
$ lsof | wc -l

LoslsofEl comando imprime una nueva línea para cada archivo abierto con información básica sobre ese archivo. Piping en elheadEl comando le mostrará el inicio de la salida, incluidos los nombres de las columnas.

La salida delsofse puede pipetear en elwc, o recuento de palabras, comando. Los-lswitch significa que solo contará las líneas nuevas. Este número probablemente será de cientos de miles.

Puede canalizar la salida delsofengreppara encontrar líneas que contengan una cadena comopython. También puede pasar un ID de proceso, que puede ser útil si desea buscar descriptores de archivo:

$ lsof | grep python

Este comando filtrará todas las líneas que no contengan el términogrepEn este caso,python.

Si tiene curiosidad sobre el límite teórico de archivos en su sistema, puede explorar esto en sistemas basados en UNIX estudiando el contenido de un archivo especial:

$ cat /proc/sys/fs/file-max

El número es muy dependiente de la plataforma, pero es probable que sea masivo. Es casi seguro que el sistema se quedaría sin otros recursos antes de alcanzar este límite.

Sin embargo, puede preguntarse por qué el sistema operativo limita los archivos. Presumiblemente, puede manejar muchos más manejadores de archivos de lo que está dejando, ¿verdad? En la siguiente sección, descubrirá por qué le importa al sistema operativo.

¿Por qué el sistema operativo limita el manejo de archivos?

Los límites reales de la cantidad de archivos que un sistema operativo puede mantener abiertos simultáneamente son enormes. Estás hablando de millones de archivos. Pero en realidad llegar a ese límite y ponerle un número fijo no está claro. Normalmente, un sistema se quedará sin otros recursos antes de que se quede sin controladores de archivos.

El límite es conservador desde el punto de vista del sistema operativo, pero amplio desde la perspectiva de la mayoría de los programas. Desde la perspectiva del sistema operativo, cualquier proceso que llegue al límite probablemente esté filtrando los manejadores de archivos junto con otros recursos.

La fuga de recursos puede deberse a una mala práctica de programación o a un programa malicioso que intenta atacar el sistema. Esta es la razón por la que el sistema operativo impone el límite: ¡para mantenerte a salvo de los demás y de ti mismo!

Además, para la mayoría de las aplicaciones, no tiene sentido tener tantos archivos abiertos. No más de una sola operación de lectura o escritura puede suceder simultáneamente en un disco duro, por lo que no hace las cosas más rápidas si solo está tratando con archivos.

Bueno, entonces sabes que abrir muchos archivos es problemático, pero hay otras desventajas de no cerrar archivos en Python, incluso si solo abres un puñado.

¿Qué pasa si no cierras un archivo y Python se bloquea?

En esta sección, experimentarás con la simulación de un bloqueo y verás cómo afecta a los archivos abiertos. Puede utilizar una función especial en elosmódulo que saldrá sin realizar ninguna de las limpiezas que Python suele hacer, pero primero, verás cómo se limpian normalmente las cosas.

Realizar operaciones de escritura para cada comando puede ser costoso. Por esta razón, el valor predeterminado de Python es usar un búfer que recopile operaciones de escritura. Cuando el búfer se llena, o cuando el archivo se cierra explícitamente, el búfer se vacía y la operación de escritura se completa.

Python trabaja duro para limpiar después de sí mismo. En la mayoría de los casos, limpiará y cerrará proactivamente los archivos por sí solo:

# write_hello.py

file = open("hello.txt", mode="w")
file.write("Hello, world!")

Al ejecutar este código, el sistema operativo crea el archivo. El sistema operativo también escribe el contenido a pesar de que en realidad nunca vaciar o cerrar el archivo en el código. Este rubor y cierre son atendidos por una rutina de limpieza que Python realizará al final de la ejecución.

Sin embargo, a veces las salidas no están tan controladas, y un accidente puede terminar evitando esta limpieza:

# crash_hello.py

import os

file = open("crash.txt", mode="w")
file.write("Hello, world!")
os._exit(1)

Después de ejecutar el fragmento anterior, puede usarcatpara inspeccionar el contenido del archivo que acaba de crear:

$ cat crash.txt
$ # No output!

Verás que a pesar de que el sistema operativo ha creado el archivo, no tiene ningún contenido. La falta de producción se debeos._exit()pasa por alto la rutina habitual de salida de Python, simulando un bloqueo. Dicho esto, incluso este tipo de simulación está relativamente controlada porque supone que Python, en lugar de su sistema operativo, se ha estrellado.

Detrás de escena, una vez que Python haya terminado, el sistema operativo también realizará su propia limpieza, cerrando todos los descriptores de archivos abiertos por el proceso. Los bloqueos pueden ocurrir en muchos niveles e interferir con la limpieza del sistema operativo, dejando los manejadores de archivos colgando.

En Windows, por ejemplo, los controladores de archivos colgantes pueden ser problemáticos porque cualquier proceso que abra un archivo también lo bloquea. Otro proceso no puede abrir ese archivo hasta que se cierre. Los usuarios de Windows pueden estar familiarizados con los procesos maliciosos que no le permiten abrir o eliminar archivos.

¿Qué es potencialmente peor que estar bloqueado fuera de los archivos? La filtración de los manejadores de archivos puede presentar un riesgo de seguridad porque los permisos asociados con los archivos a veces se confunden.

Nota: La implementación más común de Python, CPython, va más allá en la limpieza de sus manejadores de archivos colgantes de lo que podría pensar. Utiliza el conteo de referencias para la recolección de basura para que los archivos se cierren una vez que ya no se hace referencia a ellos. Dicho esto, otras implementaciones, como PyPy, utilizan diferentes estrategias que pueden no ser tan agresivas en la limpieza de los identificadores de archivos no utilizados.

¡El hecho de que algunas implementaciones no limpien tan efectivamente como CPython es otro argumento para usar siempre un administrador de contexto!

Los manejadores de archivos que se filtran y el contenido que se pierde en un búfer son lo suficientemente malos, pero un bloqueo que interrumpe una operación de archivo también podría resultar en corrupción de archivos. Esto aumenta significativamente el potencial de pérdida de datos. Una vez más, estos son escenarios poco probables, pero pueden ser costosos.

Nunca puede aislarse totalmente de un accidente, pero puede reducir su exposición utilizando un administrador de contexto. La sintaxis de un administrador de contexto naturalmente lo llevará a codificar de una manera que mantiene un archivo abierto solo durante el tiempo que sea necesario.

Conclusión

Has aprendido por qué es importante cerrar archivos en Python. Debido a que los archivos son recursos limitados administrados por el sistema operativo, asegurarse de que los archivos se cierren después de su uso protegerá contra problemas difíciles de depurar, como quedarse sin controladores de archivos o experimentar datos dañados. La mejor defensa es siempre abrir archivos con un gestor de contexto.

Al excavar debajo de la superficie, has visto lo que sucede cuando abres demasiados archivos y has provocado un bloqueo que hace que el contenido de un archivo desaparezca. Para obtener más información sobre cómo abrir archivos, consulte Lectura y escritura de archivos en Python. Para una guía detallada de los gestores de contexto, echa un vistazo a Context Managers y Python’swithDeclaración.

Descarga gratuita: obtenga un capítulo de ejemplo de CPython Internals: Your Guide to the Python 3 Interpreter que le muestra cómo desbloquear el funcionamiento interno del lenguaje Python, compilar el intérprete de Python desde el código fuente y participar en el desarrollo de CPython.

Enlaces Externos

Entradas relacionadas

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *