Archivos, rutas y JSON

Casi todo programa acaba leyendo o escribiendo algo. Esta lección va de pathlib, que sustituyó a os.path hace diez años y aún no todos se han enterado, y de los dos formatos que te vas a encontrar siempre: JSON y CSV.

pathlib: las rutas son objetos

from pathlib import Path

base = Path("datos")
f = base / "informe.txt"        # el / une rutas
f.name      # informe.txt
f.stem      # informe
f.suffix    # .txt
f.parent    # datos
f.exists()
salidaruta      datos/informe.txt
name      informe.txt   stem informe   suffix .txt
parent    datos
¿existe?  False

Ese / entre un Path y un texto no es una división: es el operador de unión de rutas, y funciona igual en Windows. Sustituye a os.path.join, y con él a casi todo os.path.

Si ves os.path.join, os.listdir o rutas como cadenas en un tutorial, es de antes de 2016. pathlib es lo que se usa hoy: menos código, menos errores con las barras y métodos que se leen.

Leer y escribir

base.mkdir(parents=True, exist_ok=True)
f.write_text("primera línea\nsegunda línea\n", encoding="utf-8")
f.read_text(encoding="utf-8")
f.read_text(encoding="utf-8").splitlines()
f.stat().st_size
salidaescrito, 30 bytes
leído:    'primera línea\nsegunda línea\n'
líneas:   ['primera línea', 'segunda línea']

parents=True crea las carpetas intermedias y exist_ok=True evita el error si ya existe; juntos equivalen a mkdir -p.

Pon siempre encoding="utf-8". Sin él, Python usa la codificación por omisión del sistema, que en Windows no es UTF-8: el mismo programa funciona en tu portátil y falla en otra máquina con un UnicodeDecodeError. Es uno de los fallos más molestos de diagnosticar, y se evita escribiendo nueve caracteres.

Sin cargarlo todo en memoria

with f.open(encoding="utf-8") as fh:
    for i, linea in enumerate(fh, 1):
        print(i, linea.rstrip())
salida1: primera línea
2: segunda línea

Un archivo abierto es iterable: el for va línea a línea sin leerlo entero. Con un registro de varios gigas, read_text() no es opción y esto sí.

Recorrer carpetas

base.iterdir()          # lo que hay dentro, un nivel
base.glob("*.txt")      # por patrón
base.rglob("*.txt")     # y en todas las subcarpetas
salidaiterdir:      ['informe.txt', 'nota.md', 'sub']
glob *.txt:   ['informe.txt']
rglob **:     ['informe.txt', 'sub/otro.txt']

Los tres devuelven generadores, no listas: para ordenarlos hace falta sorted(...) alrededor, como en la salida de arriba.

JSON

import json

texto = json.dumps(datos, ensure_ascii=False, indent=2)
vuelta = json.loads(texto)
salida{
  "nombre": "Ana",
  "edad": 30,
  "lenguajes": [
    "python",
    "go"
  ],
  "activo": true,
  "saldo": null
}
ida y vuelta igual: True

Dos argumentos que casi siempre quieres: ensure_ascii=False, para que los acentos salgan como acentos y no como \u00e1; e indent=2 si el archivo lo va a leer una persona.

Python JSON
dict objeto
list, tuple array (las tuplas vuelven como listas)
str string
int, float number
True / False true / false
None null

Y lo que no sabe convertir:

salidaTypeError: Object of type date is not JSON serializable

Fechas, Decimal, conjuntos y tus propias clases hay que convertirlos a mano, con el argumento default= de dumps o pasando primero por dataclasses.asdict.

CSV, que es otro módulo

import csv

w = csv.writer(f, delimiter=";")
w.writerow(["nombre", "ciudad", "precio"])

for fila in csv.DictReader(f, delimiter=";"):
    print(fila["nombre"])
salidaescrito: 'nombre;ciudad;precio\r\n"bufanda ""larga""";A Coruña;9.00\r\n'
leído:   {'nombre': 'bufanda "larga"', 'ciudad': 'A Coruña', 'precio': '9.00'}

Fíjate en que el módulo entrecomilla solo el campo que llevaba comillas dentro, y al leerlo lo devuelve entero. Por eso se usa csv y no linea.split(";"), que se rompe con el primer campo que contenga el separador.

DictReader usa la primera fila como nombres de columna y devuelve diccionarios, que se leen mucho mejor que fila[2]. Y al abrir el archivo para escribir CSV, hazlo con newline="", o en Windows saldrán líneas en blanco.

Para… Usa
Construir una ruta Path("a") / "b"
Crear carpetas p.mkdir(parents=True, exist_ok=True)
Leer un archivo pequeño p.read_text(encoding="utf-8")
Leer uno grande for linea in p.open(...)
Buscar archivos p.rglob("*.txt")
JSON con acentos json.dumps(x, ensure_ascii=False)
CSV el módulo csv, nunca split

Todo el código de esta lección se ejecutó con Python 3.14 en un contenedor limpio antes de publicarla; las salidas y los errores están copiados de esa ejecución.