Archivos
En resumen: Un archivo guarda los datos en el disco incluso después de que el programa termina; en Python open() abre el archivo para leer o escribir, y with lo cierra por ti.
Problema
El programa olvida
Estás escribiendo un programa para tus gastos. Los anotas por la noche, lo cierras, y al día siguiente no sabe nada de ayer. Aquí está el ejemplo más corto:
visits = 0visits += 1print(f"Ejecutado {visits} vez.")Ejecutado 1 vez.Da igual cuántas veces lo ejecutes: siempre dice «1 vez». Las variables viven en la memoria mientras el programa funciona. Cuando termina, la memoria se libera y todo desaparece.
Para recordar, el programa tiene que escribir los datos donde se quedan después de que termine: en un archivo en el disco.
La idea
Un archivo con open y with
Aquí tienes un programa completo que escribe una nota en un archivo y la vuelve a leer:
try: with open("notes.txt", "w", encoding="utf-8") as file: file.write("Comprar leche\nPagar la luz antes del 15\n") with open("notes.txt", encoding="utf-8") as file: text = file.read() print(text, end="")except OSError as e: print(f"Problema con el archivo: {e}")Comprar lechePagar la luz antes del 15open(nombre, modo)abre el archivo."notes.txt"es un archivo con ese nombre en la carpeta desde la que se ejecutó el programa. El modo"w"(write, «escribir») es para escribir: crea el archivo si no existe y lo vacía si existe. Sin modo, el archivo se abre para leer.with ... as file:te da el archivo abierto con el nombrefiley lo cierra solo cuando terminan las líneas con sangría. Más adelante verás por qué importa cerrarlo.file.write(texto)escribe, yfile.read()lee todo el archivo como una sola cadena.encoding="utf-8"dice cómo las letras se convierten en bytes. Escríbelo siempre: sin él, Python usa la codificación de la computadora, y en Windows a menudo no es UTF-8, así que letras como «ñ», «é» o «€» salen mal.OSErrores la excepción general para un problema con un archivo: el disco está lleno, el archivo está bloqueado o no existe. Python no te obliga a capturarla (el compilador de Java sí lo exige), pero sinexceptel programa se detiene.
Ejecuta el programa y abre la carpeta: notes.txt está ahí, y puedes abrirlo con cualquier editor de texto. La próxima ejecución lo encontrará.
La idea
Añade y lee las líneas
Los gastos se añaden uno por uno. El modo "a" (append, «añadir») añade al final, en lugar de escribir el archivo desde cero. read().splitlines() divide en líneas el texto leído:
try: with open("expenses.txt", "w", encoding="utf-8") as file: file.write("Almuerzo;12.50\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Taxi;8.20\n") with open("expenses.txt", "a", encoding="utf-8") as file: file.write("Libro;18.90\n") with open("expenses.txt", encoding="utf-8") as file: lines = file.read().splitlines() total = 0 for line in lines: parts = line.split(";") total += float(parts[1]) print(f"{parts[0]}: {parts[1]} €") print(f"{len(lines)} gastos, total {total:.2f} €")except OSError as e: print(f"Problema con el archivo: {e}")Almuerzo: 12.50 €Taxi: 8.20 €Libro: 18.90 €3 gastos, total 39.60 €lines es una lista de cadenas, una por cada línea, sin el carácter de salto de línea. La recorres con for, y len(lines) es el número de líneas. Hay más sobre las listas en el siguiente módulo, «Colecciones».
Más precisamente, para curiosos
"a"crea el archivo si no existe, así que la primera vez que añades no hay error.read()lee todo el archivo en la memoria. Para un archivo de varios gigabytes es mejor leer línea por línea. Mira la tarjeta sobrefor line in file.- En el código de otras personas también verás
file.readlines(). También da una lista con las líneas, pero cada una conserva su"\n"al final.
Pruébalo
Dos turnos
¿Qué imprimirá el programa?
try: with open("shift.txt", "w", encoding="utf-8") as file: file.write("Primer turno: Carlos\n") with open("shift.txt", "w", encoding="utf-8") as file: file.write("Segundo turno: Lucía\n") with open("shift.txt", encoding="utf-8") as file: print(file.read(), end="")except OSError as e: print(e)"w" vacía el archivo en cuanto lo abre, y después escribe el contenido nuevo. La primera línea ya no está, incluso antes de la lectura. Es la razón más común de «se me borró el archivo».
La idea
Cuando el archivo no está
La configuración del programa está en config.txt, y alguien lo borró. Si nadie captura la excepción:
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
print(read_config())Traceback (most recent call last): File "/app/app.py", line 6, in <module> print(read_config()) ^^^^^^^^^^^^^ File "/app/app.py", line 2, in read_config with open("config.txt", encoding="utf-8") as file: ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^FileNotFoundError: [Errno 2] No such file or directory: 'config.txt'Python ordena las llamadas al revés que Java: arriba está la más externa, la línea 6, donde se llama a read_config(). Debajo está la línea 2 en read_config, donde open no encuentra el archivo. Abajo del todo está el error en sí: FileNotFoundError y el mensaje. Por eso al principio dice «most recent call last»: la última llamada está abajo.
FileNotFoundError es una subclase de OSError. Captúrala aparte, antes del caso general:
import os
def read_config(): with open("config.txt", encoding="utf-8") as file: return file.read()
try: print(read_config())except FileNotFoundError as e: print(f"No hay archivo {e.filename}. Uso la configuración por defecto.")except OSError as e: print(f"No puedo leer la configuración: {e}")print(os.path.exists("config.txt"))No hay archivo config.txt. Uso la configuración por defecto.Falsee.filename es el nombre del archivo que falta. os.path.exists(path) comprueba si el archivo existe. La comprobación no reemplaza a except: el archivo puede desaparecer entre la comprobación y la lectura, y hay otras razones por las que la lectura puede fallar.
La idea
Línea por línea y with
El archivo de open se puede escribir por partes y leer línea por línea, sin que esté entero en la memoria. Mantiene el archivo abierto mientras trabajas con él, así que hay que cerrarlo al final:
try: with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Día {day}: {day * 120} €\n")except OSError as e: print(f"No puedo escribir: {e}")
try: with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))except OSError as e: print(f"No puedo leer: {e}")Día 1: 120 €Día 2: 240 €Día 3: 360 €writeescribe texto. El salto de línea no aparece solo: el\nlo escribes tú.for line in fileda las líneas una por una, cada una con su\nal final.rstrip("\n")lo quita; si no,printdejaría una línea vacía entre ellas.withcierra el archivo cuando terminan las líneas con sangría, sea como sea: incluso con una excepción.
Cerrar no es un trámite: mientras el archivo está abierto, el texto puede estar todavía en su búfer en la memoria, no en el disco. Para que el código sea corto, aquí no hay try:
import os
with open("report.txt", "w", encoding="utf-8") as out: for day in range(1, 4): out.write(f"Día {day}: {day * 120} €\n") print(f"En el bloque: {os.path.getsize('report.txt')} bytes")print(f"Después del bloque: {os.path.getsize('report.txt')} bytes")with open("report.txt", encoding="utf-8") as file: for line in file: print(line.rstrip("\n"))Paso a paso, en palabras
- os tiene la función para el tamaño del archivo. A la izquierda está la memoria del programa, a la derecha el disco.
- open con "w" crea el archivo vacío report.txt y un objeto de archivo con un búfer en la memoria.
- write escribe en el búfer. Al disco todavía no llega nada.
- El archivo en el disco todavía tiene 0 bytes.
- El bloque with termina y cierra el archivo. El búfer va al disco: el archivo pasa a tener 48 bytes.
- 48 bytes para 39 caracteres con los saltos de línea: en UTF-8 cada letra latina sin tilde ocupa 1 byte, la «í» ocupa 2 y el € ocupa 3.
- for line in file vuelve a leer las tres líneas del disco.
Lo prometimos en la lección sobre finally. Aquí está la misma escritura sin with, con finally:
out = open("report.txt", "w", encoding="utf-8")try: out.write("Día 1: 120 €\n")finally: out.close()with hace exactamente esto por ti: cierra el archivo, pase lo que pase en el bloque.
Más precisamente, para curiosos
- Un solo
withpuede tener varios archivos, separados con una coma:with open("app.log", encoding="utf-8") as src, open("errors.log", "w", encoding="utf-8") as out:. - Búfer. El archivo reúne el texto en la memoria y lo envía al disco en partes grandes. Cada escritura separada en el disco es lenta, y así hay pocas.
file.flush()envía el búfer enseguida, sin cerrar el archivo. - Bytes y caracteres. Por debajo, un archivo es una secuencia de bytes. Los modos
"rb"y"wb"leen y escriben bytes: para fotos, archivos comprimidos, todo lo que no es texto. Sinbel archivo es de texto, y convierte los bytes en caracteres según elencoding. file.readline()lee una línea. Cuando el archivo se termina, devuelve una cadena vacía"". Una línea vacía del archivo es"\n", así que las dos no se confunden.
Pruébalo
¿Cuándo se cierra?
Cualquier clase con los métodos __enter__ y __exit__ puede ir en un with. __enter__ se llama al principio del bloque, y __exit__ al final, incluso con una excepción. Aquí está la impresora de la caja de la lección sobre finally, como recurso:
¿Qué imprimirá el programa?
class Printer: def __enter__(self): print("La impresora está ocupada.") return self
def __exit__(self, exc_type, exc, tb): print("La impresora está libre.")
try: with Printer(): print("Imprimiendo el recibo...") qty = int("tres") print(f"Cantidad: {qty}")except ValueError: print("Cantidad incorrecta.")int("tres") lanza ValueError, y la línea «Cantidad» se salta. Al salir de with, Python llama a __exit__ enseguida, incluso antes de except. Por eso «libre» aparece antes de «Cantidad incorrecta.» Cuando except se ejecuta, la impresora ya está liberada.Pruébalo
Encuentra el error
0. ¿En qué línea está el error?Elige la línea con el error.
with y no se cierra. El texto sigue en el búfer en la memoria y no ha llegado al disco, así que en la lectura el archivo está vacío. El programa no da ningún error: cuenta mal sin avisar. Ábrelo con with open(...) as out: y lee el archivo después del bloque: entonces está cerrado y la línea está ahí.La idea
Carpetas
Las copias de seguridad de la caja van a una carpeta, una por día:
import os
backup = "backup"try: os.makedirs(os.path.join(backup, "old"), exist_ok=True) with open(os.path.join(backup, "2026-10-01.csv"), "w", encoding="utf-8") as file: file.write("Café,2.40\n") with open(os.path.join(backup, "2026-10-02.csv"), "w", encoding="utf-8") as file: file.write("Café,2.40\nTé,1.80\n") for name in sorted(os.listdir(backup)): p = os.path.join(backup, name) if os.path.isdir(p): print(f"{name}/") else: print(f"{name} ({os.path.getsize(p)} bytes)") os.remove(os.path.join(backup, "2026-10-01.csv")) print(os.path.exists(os.path.join(backup, "2026-10-01.csv")))except OSError as e: print(e)2026-10-01.csv (11 bytes)2026-10-02.csv (20 bytes)old/False| Función | Qué hace |
|---|---|
os.makedirs(p, exist_ok=True) |
crea la carpeta y todas las carpetas por encima de ella, si no existen |
os.path.join(dir, "nombre") |
la ruta a nombre dentro de dir |
os.path.isdir(p), os.path.exists(p) |
si es una carpeta, si existe |
os.path.getsize(p) |
el tamaño en bytes |
os.remove(p) |
borra un archivo (os.rmdir(p) borra una carpeta vacía) |
os.listdir(dir) |
una lista con los nombres de todo lo que hay en la carpeta |
Los tamaños son 11 y 20 bytes, aunque los caracteres son menos: en UTF-8, la «é» de «Café» y de «Té» ocupa 2 bytes.
os.listdir da los nombres sin ningún orden concreto, así que los ordenamos con sorted.Más precisamente, para curiosos
os.rmdirborra solo una carpeta vacía. Si no, daOSError: [Errno 39] Directory not empty.shutil.rmtree(p)(el móduloshutil) borra la carpeta con todo lo que tiene dentro, así que ten cuidado con él.shutil.copy(origen, destino)copia un archivo, yos.rename(origen, destino)lo mueve o le cambia el nombre.- En mucho código verás el módulo
pathlib:Path("backup") / "old"es la ruta a la carpetaolddentro debackup, yp.exists()yp.read_text(encoding="utf-8")son métodos de la ruta. Es lo mismo, con objetos en lugar de funciones. - La ruta
"notes.txt"es relativa: a la carpeta desde la que se ejecutó el programa."/home/ana/notes.txt"es absoluta.os.path.abspath("notes.txt")muestra exactamente dónde está el archivo.
Ejercicio
Programas que recuerdan
Resuelve las tareas en tu editor. En cada tarea, el programa primero crea él mismo el archivo de entrada con open(..., "w"), para que puedas probarlo enseguida.
1. Un archivo de configuración. Crea server.conf con este contenido, después léelo e imprime cada ajuste como clave: valor. Sáltate las líneas vacías y las líneas que empiezan con #. Pista: split("=", 1) divide solo en el primer = (el número es cuántas veces cortar).
# configuración del servidormax_connections=500log_file_path=/var/log/server.log
db_timeout=30max_connections: 500log_file_path: /var/log/server.logdb_timeout: 302. Un filtro de archivo de registro. Crea app.log con cuatro líneas (dos INFO y dos ERROR). Lee un nivel del teclado, por ejemplo ERROR, y escribe las líneas con ese nivel en errors.log línea por línea, con los dos archivos en un solo with. Imprime errors.log y cuántas líneas escribiste.
ERRORERROR: Failed to load configurationERROR: Null pointer exceptionEscribí 2 líneas en errors.log3. El contenido de una carpeta. Crea una carpeta project con los archivos document.txt, image.jpg, script.js y la carpeta backup. Escribe sus nombres, ordenados alfabéticamente, en directory_contents.txt, con una / después de los nombres de las carpetas. Imprime el archivo.
backup/document.txtimage.jpgscript.js4. Un archivo índice. Crea text.txt con las tres líneas de abajo. Lee una palabra del teclado y escribe en index.txt cada lugar donde aparece, en la forma línea:posición: el número de línea (desde 1) y el índice dentro de la línea (desde 0). Si la palabra está dos veces en una línea, son dos entradas.
hello worldworld hellohello again, hellohello1:02:63:03:13Más tareas
- Contador de ejecuciones. Arregla el programa de la primera tarjeta: cuenta las ejecuciones en
visits.txt. Si el archivo no existe, empieza desde 0. - Buscar y reemplazar en un archivo. La tarea de la lección «Expresiones regulares: grupos y reemplazo»: lee un archivo, reemplaza «viejo» por «nuevo» y escribe el resultado en un archivo nuevo.
- Fechas de un archivo. La tarea de las fechas de la lección «Expresiones regulares»: lee las fechas de un archivo, una por línea.
- Una copia con números de línea. Lee un archivo y escribe una copia en la que cada línea empieza con su número:
1: ...,2: ....
Test
Ponte a prueba
Lecciones relacionadas
Sección «Lecciones relacionadas»- Se basa en: Interfaces como clases abstractas · Excepciones · try, except y finally · Cadenas
- Continúa en: Datos en archivos · Códigos hash y funciones hash · Programación en red · Bases de datos
- Ejercicios sobre el tema: Texto y archivos