Leer y escribir archivos en Python sin perder datos: modos, contexto y errores

Leer y escribir un archivo en Python parece una tarea de tres verbos: abrir, leer y guardar. El problema aparece cuando el archivo ya existe, cuando el programa se interrumpe a mitad de una operación o cuando el contenido usa una codificación distinta de la que esperabas. En esos casos, la sintaxis puede ser correcta y aun así el resultado ser pérdida de datos, texto ilegible o un programa que falla lejos de la línea que causó el problema.

La función open() no es solo una puerta de entrada. Sus argumentos deciden qué permisos tendrá el programa sobre el archivo, qué ocurrirá con el contenido anterior, cómo se convertirán los bytes en texto y cuándo se liberará el recurso. Aprender a trabajar con archivos significa controlar esas decisiones de forma explícita.

El primer error ocurre antes de leer

La llamada básica tiene esta forma:

archivo = open("notas.txt", "r", encoding="utf-8")

El primer argumento identifica el camino. El segundo, el modo. El tercero indica cómo decodificar o codificar el texto. La referencia oficial de la función `open()` documenta más opciones, incluidos modos de texto y binarios. Aunque Python ofrece valores predeterminados, escribir el modo y la codificación de manera explícita hace que la intención sea más fácil de revisar.

El modo r abre para lectura y espera que el archivo exista. El modo w abre para escritura y trunca el contenido existente antes de escribir. El modo a conserva el contenido y añade al final. El modo x intenta crear un archivo nuevo y falla si el camino ya existe. La diferencia entre una letra y otra puede ser la diferencia entre actualizar un informe y borrar el informe anterior.

ModoIntenciónRiesgo que debes revisar
rLeer un archivo existenteFalla si el camino no existe
wReemplazar el contenidoBorra lo anterior al abrir
aAñadir al finalPuede duplicar registros si no controlas cada ejecución
xCrear sin sobrescribirFalla cuando el archivo ya existe

Si tu programa genera un archivo que no debe sobrescribirse por accidente, x expresa mejor la intención que w. Si necesitas actualizar un archivo completo, w puede ser correcto, pero conviene escribir primero en un archivo temporal y reemplazar el original solo cuando la operación haya terminado correctamente. Esa precaución pertenece al diseño del programa, no a un truco de sintaxis.

Leer todo no siempre es la mejor lectura

Para archivos pequeños, read() es directo:

with open("notas.txt", "r", encoding="utf-8") as archivo:
    contenido = archivo.read()

print(contenido)

El método devuelve una cadena con todo el contenido restante desde la posición actual. Puedes pasar un número para leer una cantidad limitada de caracteres, o usar readline() para obtener una línea. También puedes iterar directamente sobre el objeto de archivo:

with open("registro.log", "r", encoding="utf-8") as archivo:
    for linea in archivo:
        if "ERROR" in linea:
            print(linea.rstrip())

La documentación del tutorial oficial de Python muestra tanto la lectura completa como la lectura línea por línea. La elección depende del tamaño y del objetivo. Cargar un archivo de algunos kilobytes en memoria puede ser perfectamente razonable; cargar un log de varios gigabytes para buscar una palabra no lo es. Iterar permite procesar una parte y descartar la anterior.

También importa la posición interna. Después de leer, el cursor ya no está al principio. Si necesitas volver a leer, puedes usar seek(0), pero antes pregunta si realmente quieres repetir la lectura o si sería más claro abrir el archivo de nuevo. La claridad del flujo suele valer más que reutilizar un objeto en exceso.

Escribir tiene dos decisiones diferentes

Escribir texto no significa necesariamente “añadir una frase”. Con el modo w, el archivo se vacía al abrirse. El método write() recibe una cadena y devuelve el número de caracteres escritos:

lineas = ["Primera línea\n", "Segunda línea\n"]

with open("salida.txt", "w", encoding="utf-8") as archivo:
    archivo.writelines(lineas)

Si quieres sumar una entrada a un registro existente, usa a y añade un separador que permita distinguir una ejecución de otra:

with open("eventos.log", "a", encoding="utf-8") as archivo:
    archivo.write("Tarea completada\n")

El modo a no deduplica ni valida tu información. Si el programa se ejecuta dos veces, escribirá dos veces. Para datos que deben ser únicos, lee el estado, comprueba la condición y diseña una estrategia de actualización; no esperes que el modo de apertura resuelva una regla de negocio.

Otro detalle es la conversión a texto. write() espera una cadena en modo texto, por lo que un número debe convertirse de manera explícita o interpolarse:

cantidad = 3
with open("resumen.txt", "w", encoding="utf-8") as archivo:
    archivo.write(f"Archivos procesados: {cantidad}\n")

with es una garantía de ciclo de vida

Es posible abrir un archivo, trabajar con él y cerrarlo manualmente:

archivo = open("datos.txt", encoding="utf-8")
try:
    contenido = archivo.read()
finally:
    archivo.close()

Pero el patrón recomendado es:

with open("datos.txt", "r", encoding="utf-8") as archivo:
    contenido = archivo.read()

Al abandonar el bloque with, Python cierra el archivo incluso si una excepción interrumpe la operación. La ventaja no es estética: evita dejar recursos abiertos, facilita el flush de datos pendientes y hace visible qué parte del programa depende del archivo. La documentación oficial del tutorial explica este comportamiento junto con los métodos de los objetos de archivo.

El archivo debe estar abierto el tiempo mínimo necesario. No mantengas un descriptor abierto mientras el programa hace una llamada de red, espera entrada del usuario o procesa una tarea independiente. Lee o escribe dentro del bloque y transforma los datos fuera de él cuando sea posible.

La codificación decide si el texto sobrevive

Los archivos de texto almacenan bytes. Python debe convertir esos bytes en caracteres al leer y convertir caracteres en bytes al escribir. Ese proceso usa una codificación. Si el archivo contiene acentos, eñes o símbolos y el programa asume una codificación diferente, puede aparecer un UnicodeDecodeError al leer o un UnicodeEncodeError al escribir.

Por eso encoding="utf-8" suele ser una buena decisión explícita para archivos de texto creados por aplicaciones modernas, siempre que el formato que recibes realmente use UTF-8. No debes añadirlo como una fórmula ciega: si una fuente externa declara otra codificación, respeta ese contrato o conviértela conscientemente.

La codificación y el modo binario no son la misma cosa. Un archivo de imagen, un PDF o un archivo comprimido no debe abrirse como texto. Usa "rb" o "wb" para trabajar con bytes y evita modificar esos datos mediante operaciones de cadenas.

Un error útil es más específico que un mensaje genérico

Las operaciones de archivo fallan por razones distintas. Un camino inexistente no se soluciona igual que una falta de permisos, un archivo que dejó de ser accesible o un contenido con formato inválido. La documentación oficial sobre errores y excepciones distingue los errores de sintaxis de las excepciones que aparecen durante la ejecución y recomienda manejar tipos específicos con try y except.

try:
    with open("config.json", "r", encoding="utf-8") as archivo:
        configuracion = archivo.read()
except FileNotFoundError:
    print("No se encontró el archivo de configuración.")
except PermissionError:
    print("El programa no tiene permiso para leer este archivo.")

Evita comenzar con except: o except Exception: para ocultar todo. Si capturas una excepción demasiado amplia, el programa puede continuar con datos incompletos y el error aparecerá más tarde, en un lugar menos comprensible. Captura lo que puedes manejar y deja subir lo que todavía no sabes resolver.

Si la lectura puede devolver un formato inválido, separa el problema de acceso del problema de contenido. Un archivo que no existe es una situación del sistema de archivos; un JSON mal formado es una situación del parser. Esta separación produce mensajes mejores y permite decidir si crear un archivo, pedir otro camino o rechazar la entrada.

Para practicar el análisis de rastreos y localizar la línea que origina una excepción, puedes consultar nuestra guía sobre cómo leer un traceback de Python. También es útil revisar cómo manejar excepciones con try/except antes de añadir control de errores a un script más grande.

Una rutina pequeña para decidir el patrón correcto

Cuando necesites trabajar con un archivo, recorre esta secuencia:

  1. Define si leerás, reemplazarás, anexarás o crearás sin sobrescribir.
  2. Decide si el contenido es texto o bytes.
  3. Establece la codificación cuando trabajes con texto.
  4. Abre el recurso dentro de un bloque with.
  5. Elige lectura completa, por líneas o por fragmentos según el tamaño.
  6. Captura solo las excepciones que puedas tratar de forma útil.
  7. Prueba el caso normal, el camino inexistente, los permisos y un contenido inesperado.

Esta rutina es más importante que memorizar cinco métodos aislados. open(), read(), write(), readline() y writelines() son herramientas; la seguridad del resultado depende de cómo las combines.

Si estás construyendo hábitos de mantenimiento, puedes relacionar esta práctica con nuestras recomendaciones para escribir código Python limpio y con la guía de funciones incorporadas de Python. El objetivo no es usar más abstracciones, sino hacer que la intención del programa quede visible antes de que ocurra el error.

Preguntas que conviene responder antes de abrir

¿Cuál es la diferencia entre w y a?

w abre el archivo para escribir y elimina su contenido anterior al abrirlo. a conserva lo existente y posiciona la escritura al final. Elige según si quieres reemplazar el estado o registrar una nueva entrada.

¿Por qué debo especificar encoding="utf-8"?

Porque la codificación define cómo se convierten bytes y caracteres. Especificarla hace explícita una suposición importante y evita depender de la configuración predeterminada del sistema, siempre que UTF-8 sea realmente la codificación del archivo.

¿with open() cierra el archivo automáticamente?

Sí. Al salir del bloque, Python libera el recurso incluso si el código interno produce una excepción. Ese es el motivo por el que el patrón es preferible al cierre manual disperso.

¿Cuándo debo leer línea por línea?

Cuando el archivo puede ser grande o cuando cada línea representa una unidad de trabajo. Iterar sobre el archivo evita cargar todo el contenido en memoria de una sola vez.

¿Debo capturar cualquier excepción para que el programa no se detenga?

No. Captura las excepciones que puedas resolver o explicar. Ocultar errores desconocidos puede dejar al programa trabajando con datos incompletos y hacer más difícil encontrar la causa.

La operación segura no es la que tiene menos caracteres. Es la que deja claro qué pasará con el contenido anterior, cómo se interpretará el texto, cuánto tiempo permanecerá abierto el recurso y qué respuesta recibirá el usuario cuando el sistema no pueda completar la operación.

Deja un comentario

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

Scroll al inicio