6. Rutas, fechas y recursos
Hasta aquí hemos hecho un poco de trampa. Tratábamos las rutas de ficheros como simples cadenas de texto y los timestamps de las boyas como str que nunca interpretábamos. Funciona en un ejemplo, pero en código de verdad se paga caro. Esta lección arregla tres costumbres con tres herramientas de la librería estándar, sin instalar nada: pathlib para las rutas, datetime para el tiempo y los context managers para los recursos que hay que abrir y cerrar.
Rutas con pathlib
Concatenar rutas a mano (data_dir + "/" + name) es frágil: cambia entre Windows y Linux, se te cuela una barra de más y acabas con data//buoy.csv. La librería estándar trae pathlib, que trata las rutas como objetos con métodos.
from pathlib import Path
data_dir = Path("data/buoys")
# Construir rutas con el operador /, sin pelearte con las barras
today_log = data_dir / "2026-01-15.csv"
# Preguntarle cosas a la ruta
today_log.exists() # ¿existe?
today_log.name # "2026-01-15.csv"
today_log.stem # "2026-01-15"
today_log.suffix # ".csv"
today_log.parent # Path("data/buoys")
Recorrer una carpeta deja de ser un problema. glob filtra por patrón:
# Todos los registros CSV de la carpeta de boyas
for log_file in data_dir.glob("*.csv"):
print(log_file.name)
Y leer o escribir un fichero pequeño es una sola línea:
content = today_log.read_text(encoding="utf-8")
La costumbre que te llevas: pasa Path por tus funciones, no str. Tu editor y el type checker (lo veremos en la última lección) te avisarán si te equivocas, y el código se lee mejor. El día que una librería antigua te pida la cadena, str(path) la saca.
El tiempo con datetime
Un timestamp guardado como "2026-01-15T10:00" es solo texto: no puedes restar dos, ni saber cuál es anterior, ni sumarle una hora. Para eso está datetime.
from datetime import datetime, timedelta, timezone
# De texto ISO 8601 a objeto datetime
reading_time = datetime.fromisoformat("2026-01-15T10:00:00+00:00")
# Aritmética de tiempo de verdad
one_hour_later = reading_time + timedelta(hours=1)
gap = one_hour_later - reading_time # un timedelta
gap.total_seconds() # 3600.0
fromisoformat lee el formato ISO 8601, el estándar que usan casi todos los sensores y APIs. timedelta representa una duración, y restar dos datetime te da una.
Naive frente a aware. Un
datetimesin zona horaria (naive) es una bomba de relojería: no sabes si esas diez de la mañana son en Londres o en Canarias. Trabaja siempre con fechas aware, ancladas a UTC, y convierte a la hora local solo para mostrarla.datetime.now(tz=timezone.utc)te da el momento actual sin ambigüedad. En ingeniería marina, con boyas repartidas por varios husos horarios, mezclar horas locales es el mismo error caro que confundir metros con pies.
¿Recuerdas que en el lector de la boya guardábamos el timestamp como str? Ahora sabes por qué era un olor. En un proyecto real, parse_reading convertiría ese campo a un datetime en la frontera, y el resto del programa trabajaría con tiempo de verdad, no con texto.
Recursos que se cierran solos: context managers
Ya has escrito with open(path) as file: muchas veces. Ese with es un context manager, y ahora toca entender qué hace y por qué importa.
Un recurso (un fichero, una conexión a un sensor, un socket de red) hay que abrirlo y, pase lo que pase, cerrarlo. Si te olvidas, o si salta una excepción por el medio, el recurso queda colgando. El with garantiza el cierre aunque el bloque reviente:
# El fichero se cierra solo al salir del bloque, incluso si hay un error dentro
with open("buoy.csv", encoding="utf-8") as file:
process(file)
Lo mejor es que puedes escribir los tuyos. La forma más limpia usa un generador, justo lo que aprendiste en la lección funcional: lo que va antes del yield es la apertura, y lo que va en el finally, el cierre.
from contextlib import contextmanager
@contextmanager
def open_sensor(port: str):
"""Abre una conexión con el sensor y garantiza que se cierra."""
connection = connect(port) # preparación, antes del yield
try:
yield connection # esto es lo que recibe el 'as'
finally:
connection.close() # limpieza, pase lo que pase
with open_sensor("/dev/ttyUSB0") as sensor:
reading = sensor.read()
# aquí la conexión ya está cerrada
El try/finally es la clave: el finally se ejecuta siempre, haya error o no. Si prefieres el enfoque de objetos de la lección anterior, un context manager también se puede escribir como una clase con los métodos __enter__ y __exit__; el decorador @contextmanager es solo la versión corta.
Esto conecta con la idea que atraviesa el curso: aislar lo que toca el mundo exterior. Un context manager pone la apertura y el cierre de un recurso en un solo sitio, para que el resto de tu código no tenga que acordarse.
Ejemplo guía: leer una carpeta de registros por ventana de tiempo
Juntemos las tres herramientas: recorrer una carpeta de CSV con pathlib, interpretar la hora de cada línea con datetime y leer cada fichero con un with. La función entrega solo las lecturas dentro de una ventana de tiempo.
La carpeta de entrada tiene un CSV por día, con la misma forma que el fichero del lector: una cabecera y luego timestamp,wave_height_m,wind_speed_kn en cada línea.
data/buoys/
├── 2026-01-14.csv
├── 2026-01-15.csv
└── 2026-01-16.csv
Y por dentro, cada fichero:
timestamp,wave_height_m,wind_speed_kn
2026-01-15T09:00,1.5,11.0
2026-01-15T10:00,1.8,12.0
2026-01-15T11:00,2.1,13.5
El lector vive en src/metocean_tools/archive.py:
from datetime import datetime
from pathlib import Path
def readings_between(folder: Path, start: datetime, end: datetime):
"""Entrega las lecturas de todos los CSV de la carpeta dentro de una ventana.
Args:
folder: Carpeta con los registros de la boya, un CSV por día.
start: Inicio de la ventana de tiempo, incluido.
end: Fin de la ventana de tiempo, incluido.
Yields:
Tuplas (datetime, float): momento y altura de ola, en metros.
"""
for log_file in sorted(folder.glob("*.csv")):
with log_file.open(encoding="utf-8") as file:
next(file) # saltar la cabecera
for line in file:
raw_time, raw_wave, _ = line.strip().split(",")
moment = datetime.fromisoformat(raw_time)
if start <= moment <= end:
yield moment, float(raw_wave)
Fíjate en cómo encaja todo: pathlib encuentra y abre los ficheros, el with garantiza que se cierran, datetime convierte el texto en tiempo comparable y el yield mantiene el consumo de memoria constante aunque la carpeta tenga cientos de ficheros, exactamente como en la lección funcional.
Y así se usaría, pidiendo solo la franja de media mañana del día 15:
window = readings_between(
Path("data/buoys"),
datetime.fromisoformat("2026-01-15T09:30"),
datetime.fromisoformat("2026-01-15T10:30"),
)
list(window) # [(datetime(2026, 1, 15, 10, 0), 1.8)]
De las tres lecturas del día 15, solo la de las 10:00 cae dentro de la ventana; las de otros días ni se miran.
Este trabajo está bajo una licencia Attribution-NonCommercial-NoDerivatives 4.0 International.
Desafíos de programación atemporales y multiparadigmáticos
Te encuentras ante un librillo de actividades, divididas en 2 niveles de dificultad. Te enfrentarás a los casos más comunes que te puedes encontrar en pruebas técnicas o aprender conceptos elementales de programación.
Comprar el libroAyúdame a seguir escribiendo
Cada café me da un empujón para escribir el siguiente artículo.
¡Claro, te invito!
Comentarios
Todavía no hay ningún comentario.