Ir al contenido
wannacode

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 = 0
visits += 1
print(f"Ejecutado {visits} vez.")
Salida
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:

notes.py
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}")
Salida
Comprar leche
Pagar la luz antes del 15
  • open(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 nombre file y lo cierra solo cuando terminan las líneas con sangría. Más adelante verás por qué importa cerrarlo.
  • file.write(texto) escribe, y file.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.
  • OSError es 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 sin except el 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}")
Salida
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 sobre for 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)
Respuesta

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:

app.py
def read_config():
with open("config.txt", encoding="utf-8") as file:
return file.read()
print(read_config())
Error
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"))
Salida
No hay archivo config.txt. Uso la configuración por defecto.
False

e.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}")
Salida
Día 1: 120 €
Día 2: 240 €
Día 3: 360 €
  • write escribe texto. El salto de línea no aparece solo: el \n lo escribes tú.
  • for line in file da las líneas una por una, cada una con su \n al final. rstrip("\n") lo quita; si no, print dejaría una línea vacía entre ellas.
  • with cierra 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
  1. os tiene la función para el tamaño del archivo. A la izquierda está la memoria del programa, a la derecha el disco.
  2. open con "w" crea el archivo vacío report.txt y un objeto de archivo con un búfer en la memoria.
  3. write escribe en el búfer. Al disco todavía no llega nada.
  4. El archivo en el disco todavía tiene 0 bytes.
  5. El bloque with termina y cierra el archivo. El búfer va al disco: el archivo pasa a tener 48 bytes.
  6. 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.
  7. 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 with puede 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. Sin b el archivo es de texto, y convierte los bytes en caracteres según el encoding.
  • 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.")
Respuesta

Pruébalo

Encuentra el error

El programa escribe un pedido y cuenta las líneas del archivo. Imprime 0. ¿En qué línea está el error?

Elige la línea con el error.

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)
Salida
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.rmdir borra solo una carpeta vacía. Si no, da OSError: [Errno 39] Directory not empty. shutil.rmtree(p) (el módulo shutil) borra la carpeta con todo lo que tiene dentro, así que ten cuidado con él.
  • shutil.copy(origen, destino) copia un archivo, y os.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 carpeta old dentro de backup, y p.exists() y p.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).

server.conf
# configuración del servidor
max_connections=500
log_file_path=/var/log/server.log
db_timeout=30
Salida
max_connections: 500
log_file_path: /var/log/server.log
db_timeout: 30

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

Entrada
ERROR
Salida
ERROR: Failed to load configuration
ERROR: Null pointer exception
Escribí 2 líneas en errors.log

3. 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.

directory_contents.txt
backup/
document.txt
image.jpg
script.js

4. 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.

text.txt
hello world
world hello
hello again, hello
Entrada
hello
index.txt
1:0
2:6
3:0
3:13

Má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

1. open("config.txt", encoding="utf-8") no encuentra el archivo, y no hay ningún try alrededor. ¿Qué pasa?
2. Abres un archivo con "w" y escribes "A\n". Después lo abres otra vez con "w" y escribes "B\n". ¿Qué hay en el archivo?
3. ¿Cuándo cierra with el archivo?
4. ¿Qué devuelve file.readline() cuando el archivo se termina?
5. Abres un archivo con open() sin with, y no lo cierras. ¿Qué puede haber en el archivo mientras el programa sigue en marcha?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·