HOWTO · Python

Resuelva el ValueError: operación de E/S en un archivo cerrado en Python

Este artículo proporciona tres ejemplos principales de cómo se produce el error de valor de E/S y cuáles son las diversas formas en que se puede resolver este error.

En esta página

Para corregir ValueError: I/O operation on closed file, realiza cada lectura, iteración o escritura mientras el objeto de archivo siga abierto. Incluye toda la operación en un bloque with; Python cierra el archivo al terminar ese bloque.

Ejecuta este ejemplo:

Los ejemplos y cualquier texto visible que muestren se conservan exactamente; los rótulos del resultado permanecen en inglés.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    with path.open("w", encoding="utf-8") as handle:
        handle.write("File is ready.\n")
    with path.open(encoding="utf-8") as handle:
        print(handle.read(), end="")
File is ready.

Entender ValueError: I/O operation on closed file

ValueError: I/O operation on closed file significa que el código intentó leer de un objeto de archivo o escribir en él después de que se cerrara. El archivo puede cerrarse al salir del bloque with open(...), cuando el código llama a close() o cuando otra parte del programa cierra el mismo objeto. El traceback identifica la operación que falló; sigue el rastro de ese objeto hasta los puntos donde se abre y se cierra.

Una instrucción with administra el ciclo de vida de un recurso. No es un bloque para gestionar excepciones: cuando la ejecución sale del bloque indentado, Python cierra el archivo automáticamente. Por eso, la indentación determina durante cuánto tiempo se puede usar el archivo. La misma regla se aplica cuando un iterador basado en un archivo lee los datos de forma diferida: crear el iterador no implica necesariamente que ya haya leído todo el contenido del archivo.

Corregir el uso de un iterador de archivo después de que se cierre su administrador de contexto

csv.reader consume las filas a medida que se itera sobre él. Si el lector se crea dentro del contexto del archivo, pero el bucle queda fuera, el contexto termina primero y el lector ya no tiene un archivo abierto del que leer. Esta estructura incorrecta consiste en with open(path) as source: rows = csv.reader(source) seguido de for row in rows: con la indentación exterior.

Mantén el bucle dentro del bloque with para que cada lectura ocurra mientras el archivo de origen sigue abierto. Este ejemplo completo crea su propio archivo CSV, lee cada fila y solo sale del contexto del archivo cuando termina la iteración.

Ejecuta este ejemplo:

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

import csv
import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "people.csv"
    path.write_text("name,role\nAda,engineer\n", encoding="utf-8")
    with path.open(newline="", encoding="utf-8") as source:
        for row in csv.reader(source):
            print(row)

La corrección clave está en la indentación del bucle, no en hacer una copia adicional de las filas en memoria. Si necesitas procesar los datos después de que se cierre el archivo, materializa las filas deliberadamente dentro del bloque, por ejemplo, con list(csv.reader(source)). En archivos grandes, procesarlos mientras el contexto sigue abierto evita conservar todas las filas a la vez.

Corregir el cierre de un archivo antes de que termine un bucle

También puede abrirse un archivo de la forma habitual y cerrarse demasiado pronto. Por ejemplo, for line in source: destination.write(line); destination.close() cierra el archivo de destino durante la primera iteración. Si se procesa otra línea, la siguiente llamada a write() intenta usar un archivo cerrado y falla. La llamada a close() afecta al objeto sobre el que se ejecuta: destination.close() no cierra source, y volver a cerrar destination tampoco cierra source.

La solución sencilla es dejar que un administrador de contexto cierre ambos archivos después del bucle. Este ejemplo copia un archivo de entrada pequeño, creado localmente, en otro archivo temporal e imprime las líneas copiadas mientras ambos contextos siguen activos.

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    source_path = Path(directory) / "source.txt"
    destination_path = Path(directory) / "copy.txt"
    source_path.write_text("first\nsecond\n", encoding="utf-8")

    with source_path.open(encoding="utf-8") as source:
        with destination_path.open("w", encoding="utf-8") as destination:
            for line in source:
                destination.write(line)
                print(line.rstrip())

En este patrón no hace falta llamar a close() manualmente. Si el bucle tiene una condición para cerrar un archivo, comprueba si esa condición debería aplicarse después del bucle o si cada iteración debería usar un archivo distinto. Mantén la limpieza en el mismo nivel estructural que el trabajo que limpia.

Corregir las escrituras que se intentan después de cerrar un archivo

Una llamada explícita a close() termina de inmediato el tiempo durante el cual se puede usar el archivo, aunque el código siga indentado dentro de un bloque with. En una secuencia como handle.write("first"), handle.close() y luego handle.write("second"), falla la última escritura. El administrador de contexto cerrará el archivo al salir del bloque, así que cerrarlo manualmente dentro de ese bloque es redundante y puede hacer que fallen operaciones posteriores.

Elimina la llamada manual a close() y coloca dentro del contexto todas las escrituras previstas. Si el código no usa un administrador de contexto, empareja cada open() que se complete correctamente con una sola llamada a close() después de la última operación; es preferible hacerlo en un bloque finally para que la limpieza también ocurra si falla otra operación. Para el manejo habitual de archivos, with es más breve y garantiza la limpieza tanto al salir normalmente como cuando se produce una excepción.

El instante justo después de close() es un límite útil para revisar: el archivo deja de poder usarse incluso antes de que termine el bloque with. Este ejemplo autónomo captura el ValueError resultante para que el programa pueda mostrar su mensaje y continuar.

El código y las etiquetas o salidas visibles de esta sección se mantienen en inglés.

import tempfile
from pathlib import Path

with tempfile.TemporaryDirectory() as directory:
    path = Path(directory) / "notes.txt"
    with path.open("w+", encoding="utf-8") as handle:
        handle.write("ready")
        handle.close()
        try:
            handle.read()
        except ValueError as error:
            print(type(error).__name__ + ": " + str(error))
ValueError: I/O operation on closed file.

Evitar errores por archivos cerrados al administrar su ciclo de vida

Usa el traceback para localizar la llamada fallida a read(), la iteración, write() u otra operación relacionada. Después, identifica el objeto de archivo concreto y averigua dónde se abrió. Comprueba todas las rutas que pueden cerrar ese objeto, incluidas las llamadas explícitas a close(), la salida de un bloque with y las funciones auxiliares que reciben el mismo objeto. Por último, revisa la indentación y la ubicación del bucle: la operación fallida debe ejecutarse antes de que termine el contexto propietario y antes de cualquier cierre explícito.

Una lista breve para el diagnóstico:

  • ¿Qué operación de archivo aparece en la última línea de la aplicación que muestra el traceback?
  • ¿Qué variable es el objeto de archivo de esa operación y dónde se abre?
  • ¿La ejecución llega a close() o sale del bloque with correspondiente antes de realizar la operación?
  • ¿Se está consumiendo un bucle, una función de devolución de llamada o un iterador diferido fuera del contexto del archivo?
  • ¿Puede un administrador de contexto mantener abierto el archivo durante todas las lecturas o escrituras?

Estas comprobaciones permiten distinguir los casos habituales: consumir un lector CSV diferido después de que se cierre el archivo de origen, cerrar un archivo de salida antes de que termine un bucle y volver a escribir después de un cierre explícito. Mantén el trabajo dentro del ciclo de vida del archivo, elimina los cierres prematuros y deja que el administrador de contexto realice la limpieza final.