1.10 · Archivos¶
Objetivos. Al terminar este capítulo podrás: leer y escribir ficheros de
forma segura con with, elegir el modo de apertura, trabajar con JSON y
manejar rutas multiplataforma con pathlib.
Evidencia de logro. Escribirás un asistente de notas que guarda y recupera datos en un fichero (y en JSON), sobreviviendo al cierre del programa.
Contexto y motivación¶
Si los datos desaparecen al cerrar el programa, no se pueden recuperar en la siguiente ejecución. Los ficheros son una forma sencilla de persistir información: notas, configuraciones, resultados o logs. En IA guardarás datasets, modelos y métricas; aquí aprenderás el mecanismo básico que después usarás con librerías más especializadas.
Dos ideas clave antes de empezar:
- Cuando abras un fichero, ciérralo (o usa
with, que lo hace por ti). De lo contrario, puede quedar abierto o escribirse solo una parte de los datos. - Para construir rutas portables, usa
pathliben lugar de concatenar cadenas con"/".
Vocabulario¶
| Término | Significado |
|---|---|
| Fichero | Datos guardados en disco (texto, JSON, binario) |
| Modo | Cómo se abre: lectura (r), escritura (w), añadir (a) |
with |
Context manager que cierra el fichero automáticamente |
| Serializar | Convertir a texto/bytes para guardar (JSON) |
| Ruta | Ubicación del fichero (/carpeta/archivo.txt) |
Prerrequisitos¶
1.9 · Cadenas y manejo básico de rutas. Aquí anticipamos
FileNotFoundError para tratar la primera ejecución; su modelo completo se
estudia en 1.11 · Excepciones y logging.
Dónde se escriben los ficheros de este capítulo
Todos los ejemplos trabajan en una carpeta de trabajo propia,
practica/ud1_archivos/, que se crea en el primer bloque. No uses la
carpeta datos/ del curso: ahí viven los ficheros fijos que usa todo el
alumnado y no deben cambiar.
El apartado 1 crea la carpeta de trabajo y el fichero notas.txt, y los
apartados siguientes reutilizan las variables carpeta y ruta. Si
copias un apartado suelto, ejecuta antes el bloque del apartado 1.
1. Escribir y leer con with¶
Para escribir, abrimos en modo "w" (crea o sobrescribe):
from pathlib import Path
carpeta = Path("practica/ud1_archivos")
carpeta.mkdir(parents=True, exist_ok=True)
ruta = carpeta / "notas.txt"
with open(ruta, "w", encoding="utf-8") as f:
f.write("hola\nmundo\n")
with open(ruta, encoding="utf-8") as f:
contenido = f.read()
print(contenido)
Salida esperada:
Este bloque crea la carpeta practica/ud1_archivos/ y dentro el fichero
notas.txt; por eso la comprobación visible es el read() que imprime su
contenido. La línea en blanco final de la salida no es un error: el texto
escrito termina en \n y print() añade otro salto de línea.
Paso a paso:
Path(...)representa una ruta (lo verás a fondo en el apartado 5) ymkdir(parents=True, exist_ok=True)crea la carpeta si no existe.open(ruta, "w", encoding="utf-8")abre el fichero para escribir y devuelve un objeto fichero, que llamamosfpor costumbre.with ... as f:abre el fichero y lo cierra automáticamente al salir del bloque indentado, aunque haya un error. Es el patrón a usar siempre.f.write(texto)escribe;f.read()devuelve todo el contenido como una cadena. Fíjate en quewriteno añade saltos de línea: hay que escribir\n.
2. Modos de apertura¶
| Modo | Qué hace |
|---|---|
"r" (por defecto) |
Lectura; error si el fichero no existe |
"w" |
Escritura; sobrescribe o crea |
"a" |
Añadir al final (append); crea si no existe |
"r+" |
Lectura y escritura (no lo trunca) |
with open(ruta, "a", encoding="utf-8") as f:
f.write("otra linea\n")
with open(ruta, encoding="utf-8") as f:
print(f.read().split())
Salida esperada:
"a" añade al final sin borrar lo anterior. Usa encoding="utf-8" para que los
acentos y caracteres no ASCII se guarden bien.
3. Leer línea a línea¶
Para ficheros grandes, leer línea a línea evita cargar todo en memoria:
Salida esperada:
Iterar el fichero (for linea in f) es la forma eficiente: solo hay una
línea en memoria a la vez. readlines() carga todas, útil para ficheros
pequeños.
4. Trabajar con JSON¶
JSON es el formato de intercambio de datos por excelencia (APIs, configs). Se
serializa con json.dump y se deserializa con json.load:
import json
datos = {"nombre": "Ana", "edad": 20, "ciudad": "Sevilla"}
with open(carpeta / "datos.json", "w", encoding="utf-8") as f:
json.dump(datos, f, ensure_ascii=False, indent=2)
with open(carpeta / "datos.json", encoding="utf-8") as f:
recuperado = json.load(f)
print(recuperado)
Salida esperada:
Este bloque crea datos.json dentro de la carpeta de trabajo y lo vuelve a leer. ensure_ascii=False conserva los
acentos; indent=2 lo formatea legible. JSON convierte dict a texto y vuelta;
es la base de cualquier API que verás en la UD2.
JSON vs texto plano
El texto plano es fácil de leer; JSON tiene estructura (claves, listas, anidados) y lo entienden casi todos los lenguajes. Para datos con forma, JSON.
5. Rutas y ficheros con pathlib¶
pathlib es la forma moderna y multiplataforma de manejar rutas y ficheros.
Un Path es un objeto que representa una ruta: puedes inspeccionarla, construirla
y leer/escribir sobre ella. Sustituye a os.path y a las cadenas con "/" que
fallan según el sistema operativo.
5.1 Crear un Path y leer sus componentes¶
from pathlib import Path
p = Path("docs/ud1/notas.txt")
print(p.name) # nombre del fichero
print(p.stem) # sin extensión
print(p.suffix) # extensión
print(p.parent.as_posix()) # carpeta contenedora, siempre con "/"
print(p.parts) # trozos de la ruta
Salida esperada:
Path funciona también con rutas que aún no existen: puedes preguntarle por
el nombre, la extensión o la carpeta sin abrir nada.
5.2 Construir rutas con /¶
El operador / une rutas de forma multiplataforma (sin concatenar cadenas):
base = Path("datos")
doc = base / "ventas" / "2026"
print(doc.as_posix()) # ruta con "/" en cualquier sistema
print((doc / "enero.csv").as_posix())
Salida esperada:
Path("datos") / "ventas" / "2026" construye la ruta correcta tanto en Windows
como en Linux/macOS. Adiós a os.path.join y a las barras escritas a mano.
Al mostrarla, as_posix() la escribe siempre con /, también en Windows.
5.3 Inspeccionar: existe, es fichero, es carpeta¶
p = carpeta / "inspeccion.txt"
p.write_text("fichero de ejemplo\n", encoding="utf-8")
print(p.exists()) # ¿existe la ruta?
print(p.is_file()) # ¿es un fichero?
print(p.is_dir()) # ¿es una carpeta?
Salida esperada:
is_dir() es False porque p apunta a un fichero, aunque esté dentro de
una carpeta. exists() solo pregunta si hay algo en esa ruta; is_file() e
is_dir() preguntan además de qué tipo es.
5.4 Leer y escribir con pathlib¶
p = carpeta / "lectura.txt"
p.write_text("hola\nmundo\n", encoding="utf-8")
print(p.read_text(encoding="utf-8").split())
with p.open(encoding="utf-8") as fichero:
for numero, linea in enumerate(fichero, start=1):
print(numero, linea.strip())
Salida esperada:
write_text()/read_text() simplifican el with open(...). Para ficheros
grandes, p.open() te da el mismo comportamiento eficiente de leer línea a
línea; úsalo dentro de with para cerrar el fichero automáticamente.
Aquí enumerate(fichero, start=1) numera las líneas empezando en 1, igual que
en 1.6. linea.strip() quita el salto de línea final que trae cada línea
leída.
5.5 Crear carpetas y listar¶
Este ejemplo usa una subcarpeta propia para que la lista no dependa de lo que haya escrito antes.
listado = carpeta / "listado"
listado.mkdir(parents=True, exist_ok=True)
(listado / "enero.csv").write_text("a,b\n1,2\n", encoding="utf-8")
(listado / "febrero.csv").write_text("a,b\n3,4\n", encoding="utf-8")
(listado / "notas.txt").write_text("sin datos\n", encoding="utf-8")
print(sorted(x.name for x in listado.iterdir())) # contenido de la carpeta
print(sorted(x.name for x in listado.glob("*.csv"))) # solo los .csv
Salida esperada:
mkdir(parents=True, exist_ok=True) crea la carpeta (y las intermedias) sin dar
error si ya existe. iterdir() lista el contenido; glob("patrón") busca por
patrón (*.csv, *.txt, **/*.py con rglob).
Fíjate en (x.name for x in listado.iterdir()): es una expresión generadora, la
misma que una lista entre corchetes pero sin construir la lista. Al envolverla
en sorted() se convierte en una lista ordenada de nombres. Ordenar es
necesario porque iterdir() no garantiza ningún orden.
5.6 Renombrar y borrar¶
p = carpeta / "listado" / "notas.txt"
p.rename(p.with_name("guardado.txt"))
print("antigua existe:", p.exists())
print("nueva existe:", p.with_name("guardado.txt").exists())
p.with_name("guardado.txt").unlink() # borra un fichero
print("existe tras unlink:", p.with_name("guardado.txt").exists())
Salida esperada:
rename() mueve o renombra; unlink() borra un fichero, y da error si el
fichero no existe. Con unlink(missing_ok=True) no da error: es útil cuando no
te importa si estaba ahí. with_name(...) te da la misma ruta con otro nombre,
sin volver a escribir la carpeta.
Para borrar una carpeta con contenido necesitas shutil.rmtree(carpeta),
que viene en el módulo shutil (import shutil) y borra todo lo que hay
dentro. Úsalo con mucho cuidado: no hay vuelta atrás.
pathlib no sustituye a open por completo
Para I/O fina (modos w/a, binario, etc.) open() sigue siendo útil;
pathlib te da una capa cómoda por encima. Conviven: elige pathlib para
rutas y operaciones habituales.
Aplicación práctica: asistente de notas¶
Un mini-sistema que guarda y recupera notas en JSON:
import json
from pathlib import Path
def guardar(notas, ruta):
with Path(ruta).open("w", encoding="utf-8") as f:
json.dump(notas, f, ensure_ascii=False, indent=2)
def cargar(ruta):
try:
with Path(ruta).open(encoding="utf-8") as f:
return json.load(f)
except FileNotFoundError:
return []
ruta = carpeta / "notas_demo.json"
ruta.parent.mkdir(parents=True, exist_ok=True)
ruta.unlink(missing_ok=True) # empieza limpio para que la salida sea estable
notas = cargar(ruta) # [] porque el fichero no existe todavia
notas.append("Comprar café")
guardar(notas, ruta)
print("Lo que hay en memoria:", notas)
print("Lo que se lee del disco:", cargar(ruta))
Salida esperada:
Fíjate en la diferencia entre las dos líneas: la primera imprime la lista que tienes en memoria, la segunda vuelve a leer el fichero y devuelve otra lista con el mismo contenido. Que coincidan es la prueba de que la escritura ha funcionado.
La función cargar() ya usa try/except para continuar si el fichero aún no
existe, por eso notas vale [] en lugar de fallar. Y unlink(missing_ok=True)
borra el fichero si existe, para que una ejecución anterior no cambie la salida.
El patrón de excepciones se verá a fondo en 1.11.
Errores frecuentes¶
- Olvidar
with(oclose()) y dejar el fichero abierto o a medias. - Usar
"w"cuando querías añadir ("w"borra el contenido anterior). - No usar
encoding="utf-8"y que los acentos se corrompan. - Leer un fichero que no existe sin manejar
FileNotFoundError. - Construir rutas concatenando cadenas (
carpeta + "/" + nombre): es fácil olvidar o duplicar separadores y mezclar\y/; usapathlib. - Usar rutas relativas sin saber desde dónde se ejecuta el programa: se resuelven desde la carpeta actual de la terminal. Ejecuta desde la raíz del proyecto.
- Llamar a
mkdir(parents=False)y fallar si el padre no existe: usamkdir(parents=True). - Llamar a
unlink()sobre una carpeta (o armdir()sobre una no vacía):unlinkes para ficheros yshutil.rmtreepara carpetas con contenido. - Leer todo el fichero cuando vas a procesarlo línea a línea (memoria).
Práctica de transferencia¶
- Escribe un fichero
log.txtcon 3 líneas y luego añade una cuarta ("a"). - Guarda una lista de diccionarios (2 contactos: nombre/email) en
contactos.jsony recupérala. Comprueba que la estructura se mantiene. - Con
pathlib, crea la estructurapractica/ud1_archivos/2026/09/, escribe un fichero en ella y lista su contenido coniterdir(). - Usa
glob("*.json")para listar los JSON de una carpeta yrglob("*.py")para encontrarlos en subcarpetas. - Amplía el asistente de notas con una función
borrar_nota(texto).
Qué debes comprobar: en el punto 1, que el fichero tiene 4 líneas y no 1 (si
solo tiene una, has abierto con "w"); en el 2, que lo recuperado es una lista
de diccionarios igual a la original (== debe dar True).
Producto evaluable¶
Cuaderno 08_archivos_log.py con:
- El asistente de notas con
guardar/cargar, leído antes y después de guardar para comprobar que persiste. Explica también qué ocurre si vuelves a ejecutar el script y no limpias la ruta de demostración. - Una celda que guarde y recupere
contactos.jsony muestre la estructura. - Una celda con
pathlibque cree una estructura de carpetas, escriba un fichero, lo liste coniterdir, lo renombre y lo borre, mostrando el estado en cada paso.
Formato de entrega: cuaderno marimo ejecutado, con salida esperada, interpretación y conclusión.
Resumen y referencia rápida¶
| Tarea | Código |
|---|---|
| Escribir (sobrescribe) | with open("ruta.txt", "w", encoding="utf-8") as f: f.write(...) |
| Leer | with open("ruta.txt", encoding="utf-8") as f: texto = f.read() |
| Añadir al final | "a" en open |
| Línea a línea | for linea in f: |
| JSON | json.dump / json.load |
| Ruta moderna | Path("a") / "b" (multiplataforma) |
| Componentes | p.name, p.stem, p.suffix, p.parent |
| Leer/escribir | p.read_text() / p.write_text(...) |
| Carpetas | d.mkdir(parents=True), d.iterdir(), d.glob("*.csv") |
| Renombrar/borrar | p.rename(nuevo), p.unlink() |
Siguiente: 1.11 · Excepciones y logging.