Lab guiado: Rutas y manejo de errores en ficheros¶
- Unidad: UT2 — Trabajo con ficheros
- Agrupamiento: Parejas (conductor / navegante)
- Recursos:
- Ficheros de trabajo:
01_estructura.py,02_rutas.py,03_lectura.py,04_excepciones.py,05_patron_completo.py,06_listar.py,07_exportar.py
Cómo trabajar en parejas¶
Este lab se realiza en parejas conductor/navegante. Leed las instrucciones antes de empezar.
Roles¶
| Rol | Qué hace |
|---|---|
| Conductor | Escribe el código/configuración y ejecuta los pasos. Solo el conductor toca el teclado. |
| Navegante | Lee el enunciado en voz alta, propone soluciones, detecta errores y formula las preguntas. |
Rotación¶
- Cambiad de rol al final de cada paso.
- Al rotar, el navegante ocupa el teclado y el conductor se convierte en navegante.
- Ambos debéis poder explicar cualquier parte del trabajo al terminar el lab.
Entrega¶
Carpeta comprimida en formato zip que contenga todos los ficheros .py y las subcarpetas datos/, exportaciones/ y logs/ generadas durante el lab.
Antes de empezar: repasemos los conceptos
pathlib.Path
Clase de Python que representa una ruta del sistema de ficheros como un objeto con atributos (name, stem, suffix, parent) y métodos propios (exists(), mkdir(), glob()…).
Operador / en pathlib
Combina objetos Path para construir rutas de forma portable entre sistemas operativos — no uses concatenación de strings para rutas.
Path(__file__).parent
Patrón habitual para obtener la carpeta donde está el script en ejecución. Permite construir rutas relativas que funcionan sin importar desde qué directorio se lanza el programa.
try / except
Bloque que captura excepciones y permite reaccionar de forma controlada. Úsalo siempre que hagas I/O: el disco puede fallar, el fichero puede no existir, los permisos pueden ser insuficientes.
FileNotFoundError y PermissionError
Las dos excepciones más habituales al trabajar con ficheros. Capturarlas por separado permite dar mensajes útiles al usuario y tomar decisiones diferentes en cada caso.
else y finally en try / except
else se ejecuta solo si no hubo excepción — úsalo para la lógica de éxito. finally se ejecuta siempre — úsalo para limpieza, logs o contadores.
Paso 1 — Crear la estructura del proyecto 01_estructura.py¶
Conceptos
Path(__file__).parent devuelve la carpeta del script en ejecución.
mkdir(parents=True, exist_ok=True) crea el directorio y todos los intermedios sin lanzar error si ya existe.
Para escribir en un fichero con open(), usa el modo "w" (crea o sobreescribe):
Objetivo¶
Crear con Python la estructura de carpetas del proyecto y generar los ficheros de datos de ejemplo que usaremos en el resto del lab.
Tarea¶
Copia este código en 01_estructura.py y ejecútalo:
Pregunta:¶
¿Qué error lanzaría
mkdir()si no pasáramosexist_ok=Truey la carpeta ya existiera?
Respuesta
Lanzaría FileExistsError. El parámetro exist_ok=True silencia ese error concreto — muy útil en scripts que se ejecutan varias veces.
Ejecuta el script: python 01_estructura.py. Comprueba que se ha creado la carpeta datos/ y el fichero notas_1a.txt.
TODO¶
Completa 01_estructura.py añadiendo lo que falta:
Pista
Para crear exportaciones/ y logs/ usa el mismo patrón: (BASE / "carpeta_datos").mkdir(exist_ok=True).
Para escribir notas_1b.txt abre el fichero con open(..., "w") igual que en el ejemplo.
Comprobación¶
Salida esperada:
Y en el sistema de ficheros deben existir las carpetas datos/, exportaciones/ y logs/.
Reflexión¶
Respuestas
Path(__file__).parentnos da la carpeta del script, no la de trabajo — el código es portable.- El operador
/construye rutas sin concatenar strings, lo que funciona igual en Linux y Windows. mkdir(exist_ok=True)hace el script idempotente: puedes ejecutarlo varias veces sin errores.- Cómo hemos visto anteriormente
open(ruta, "w")crea o sobreescribe un fichero;f.write()escribe cada línea. El bloquewithcierra el fichero automáticamente.
Paso 2 — Explorar propiedades de una ruta 02_rutas.py¶
Conceptos
Los objetos Path exponen atributos de solo lectura: name (nombre con extensión), stem (sin extensión), suffix (extensión con punto), parent (directorio padre).
exists(), is_file() e is_dir() comprueban el tipo de elemento al que apunta la ruta.
Objetivo¶
Obtener información sobre rutas usando los atributos de Path, sin manipular strings manualmente.
Tarea¶
Pregunta:¶
¿Por qué
ruta.exists()devuelveFalseen el ejemplo anterior sidatos/notas_1a.txtsí existe?
Respuesta
Porque la ruta que hemos le pasamos datos/alumnos/notas_1a.txt no existe en el disco duro. La que creamos fue datos/notas_1a.txt
Ejecuta el script: python 02_rutas.py.
TODO¶
Pista
ruta.parent devuelve otro objeto Path. Puedes encadenar: ruta.parent.parent para subir dos niveles, o ruta.parent.name para obtener solo el nombre del directorio padre.
Comprobación¶
Salida esperada:
Nombre completo: notas_1a.txt
Sin extensión: notas_1a
Extensión: .txt
Directorio padre: .../datos
Nombre directorio padre: datos
¿Existe? True
¿Es fichero? True
¿Es directorio? False
¿Padre existe? True
¿Ruta absoluta? True
Ruta absoluta C:\Users\User\pd4\ut2-a2\lab\datos\notas_1a.txt
La ruta absoluta dependerá del caso particular en el que estés ejecutando el script.
Reflexión¶
Respuestas
- Los atributos de
Pathson de solo lectura y no acceden al disco — son operaciones sobre la cadena de la ruta. exists(),is_file()eis_dir()sí acceden al disco para comprobar el estado real.- Usar
Path(__file__).parenten lugar de"."o rutas escritas directamente en el código hace el programa portable (ejecutable en otros equipos y sistemas operativos).
Paso 3 — Capturar FileNotFoundError 03_lectura.py¶
Conceptos
try / except permite reaccionar ante errores sin detener el programa.
FileNotFoundError se lanza al abrir en modo "r" un fichero que no existe.
La cláusula as e asigna la excepción a una variable para acceder al mensaje original del SO.
Objetivo¶
Leer un fichero de texto capturando el error de forma controlada si el fichero no existe.
Tarea¶
Salida esperada:
Pregunta:¶
¿Qué problema tiene capturar
Exceptionen lugar deFileNotFoundError?
Respuesta
Exception captura cualquier error, incluidos bugs del programa (como un NameError o un TypeError). Esto silencia errores que deberían verse, haciendo imposible la depuración. Captura siempre el tipo más específico posible.
Ejecuta el script: python 03_lectura.py. Verifica que el mensaje de error aparece sin que el programa se detenga abruptamente.
TODO¶
Pista
Si quieres mostrar solo el nombre del fichero en el mensaje de error, usa ruta.name en lugar de mostrar la excepción completa con e.
Comprobación¶
Si cambias la ruta a un fichero que no existe:
Reflexión¶
Respuestas
try / exceptes el mecanismo estándar de Python para gestionar errores de I/O.- Capturar
FileNotFoundErrorespecíficamente es mejor queException— solo interceptamos el caso que esperamos. - El bloque
else(que añadiste en el TODO) se ejecuta solo si no hubo excepción — es el sitio correcto para la lógica de éxito.
Paso 4 — Capturar múltiples excepciones 04_excepciones.py¶
Conceptos
Varios bloques except permiten reaccionar de forma distinta ante cada tipo de error.
Una tupla except (ErrorA, ErrorB) as e agrupa errores con la misma reacción.
OSError es la clase base de FileNotFoundError, PermissionError e IsADirectoryError.
Objetivo¶
Distinguir entre tipos de error de I/O y mostrar mensajes útiles y específicos para cada caso.
Tarea¶
Pista
Para capturar cualquier error de fichero sin distinguir el tipo, usa OSError as e — es la clase base de FileNotFoundError, PermissionError e IsADirectoryError. str(e) incluye el código de error del SO y la ruta.
Pregunta:¶
¿Cuándo usarías
except (FileNotFoundError, PermissionError) as een lugar de dos bloquesexceptseparados?
Respuesta
Cuando quieres la misma reacción ante ambos errores — por ejemplo, mostrar el mensaje original del sistema con str(e) sin distinción. Si la reacción es diferente (crear el fichero si no existe, mostrar un mensaje al usuario indicando que no tiene permisos si no hay permisos), necesitas bloques separados.
Ejecuta: python 04_excepciones.py.
TODO¶
Antes de escribir el código, crea el fichero de prueba para el error de permisos:
- Crea el fichero
datos/sin_permisos.txtcon cualquier contenido. - Con el botón derecho sobre el fichero en el explorador de archivos, quítale el permiso de lectura. Si no sabes cómo hacerlo, pregunta al profesor.
Comprobación¶
Leído: 52 caracteres.
'notas_2a.txt' no existe.
Sin permisos para leer 'sin_permisos.txt'.
Error de I/O al leer 'datos': [Errno 21] Is a directory: '.../datos'
Reflexión¶
Respuestas
- Varios bloques
exceptpermiten reaccionar de forma diferente a cada tipo de error. OSErrorcomo clase base captura cualquier error de I/O cuando no necesitamos distinguir el tipo exacto.- Envolver la lógica de I/O en una función devuelve
Noneen caso de error, el código que llama decide qué hacer con eso. - El orden de los bloques
exceptimporta: Python ejecuta el primero que coincida. Si ponesOSErrorantes queFileNotFoundErroroPermissionError, estos nunca se alcanzan porque son subclases deOSError. Coloca siempre las excepciones más específicas primero. IsADirectoryError(lanzada al abrir una carpeta como fichero) es también subclase deOSErrorpero no dePermissionError, por eso solo la captura el bloqueexcept OSError.- Los permisos del sistema de ficheros son responsabilidad del SO, no de Python. Para reproducir un
PermissionErroren pruebas hay que quitarle el permiso de lectura al fichero desde fuera del programa.
Paso 5 — Patrón completo try / except / else / finally 05_patron_completo.py¶
Conceptos
else se ejecuta solo si no hubo excepción — separa la lógica de éxito de la de error.
finally se ejecuta siempre — ideal para contadores, logs o cualquier limpieza garantizada.
Para escribir en un fichero con open(), el segundo argumento indica el modo:
Objetivo¶
Aplicar el patrón completo de cuatro bloques para leer y procesar notas de forma robusta, registrando cada intento en un fichero de log.
Tarea¶
Pregunta:¶
¿Qué diferencia hay entre poner código en
elsey ponerlo al final del bloquetry?
Respuesta
else se ejecuta únicamente cuando el bloque try termina sin ninguna excepción.
Por eso se usa para el flujo de éxito (por ejemplo, procesar datos ya leídos).
Si ese código se pone al final de try, queda dentro de la zona de riesgo:
cualquier excepción en líneas anteriores o en ese propio código corta la ejecución y puede dejar trabajo a medias.
Separarlo en else hace más claro qué parte es “intento” y qué parte es “éxito confirmado”.
Ejecuta: python 05_patron_completo.py.
TODO¶
Pista
Cada línea de notas_1a.txt tiene el formato Nombre,Nota. Para extraer la nota: _, nota_str = linea.strip().split(",") y luego float(nota_str).
Comprobación¶
El fichero logs/carga.log debe contener:
Reflexión¶
Respuestas
try / except / else / finallycubre los cuatro casos: intento, error, éxito y limpieza.elsehace el código más legible al separar el camino feliz del camino de error.finallycon escritura en log garantiza trazabilidad aunque el programa falle.
Paso 6 — Listar y procesar varios ficheros 06_listar.py¶
Conceptos
carpeta.glob("*.txt") devuelve un iterador de objetos Path filtrados por patrón.
sorted() garantiza orden alfabético — glob() no garantiza ningún orden.
Envuelve la lectura de cada fichero en su propio try / except para que un fichero corrupto no detenga el resto.
Objetivo¶
Procesar todos los ficheros .txt de la carpeta datos/ de forma robusta, calculando la nota media de cada grupo.
Tarea¶
Pregunta:¶
Investiga qué permite
iterdir()y contesta. ¿Qué ventaja tieneglob("*.txt")frente aiterdir()con unifpara filtrar?
Respuesta
glob() expresa la intención directamente en el patrón — el código es más conciso y legible. Con iterdir() necesitarías algo como if f.suffix == ".txt" and f.is_file(), que es más "largo" y por tanto más propenso a que cometamos errores.
Ejecuta: python 06_listar.py. Verifica que aparecen los dos ficheros.
TODO¶
Pista
Dentro del bucle, usa fichero.read_text(encoding="utf-8") para leer cada fichero. Envuelve esa llamada en try / except OSError para capturar cualquier error de lectura y continuar con el siguiente fichero usando continue.
Comprobación¶
Salida esperada si no hay errores:
Salida esperada si dentro de la carpeta hay un archivo sin permiso de lectura:
Error leyendo notas_2a.txt: [Errno 13] Permission denied: '.../datos/notas_2a.txt'
Grupo notas_1a: media 7.88
Grupo notas_1b: media 7.70
Salida esperada si un fichero no tiene ninguna nota válida:
Aviso: línea ignorada en notas_1a.txt: 'Ana-8.5'
Aviso: línea ignorada en notas_1a.txt: ''
Grupo notas_1a: sin datos válidos.
Grupo notas_1b: media 7.70
Reflexión¶
Respuestas
glob()combina listado y filtrado en una sola llamada — más expresivo queiterdir()+if.sorted()asegura que el orden de procesamiento es siempre el mismo — importante para reproducibilidad.continuedentro delexceptsalta al siguiente fichero sin detener el bucle completo.
Paso 7 — Exportar resumen a fichero 07_exportar.py¶
Conceptos
write_text() sobreescribe el fichero completo — conveniente cuando el contenido se genera de una sola vez.
Path.mkdir(exist_ok=True) garantiza que la carpeta de destino exista antes de escribir.
Integra todos los patrones aprendidos: rutas relativas, glob(), try/except por fichero y escritura final.
Objetivo¶
Generar un fichero de resumen en exportaciones/resumen.txt con las medias de todos los grupos, integrando todo lo aprendido en el lab.
Tarea¶
Pregunta:¶
¿Por qué usamos
read_text()en este script en lugar deopen()conwith?
Respuesta
Porque leemos todo el contenido de una sola vez y no necesitamos acceso línea a línea ni escritura incremental. read_text() es más conciso en ese caso. Si tuviéramos que procesar ficheros muy grandes línea a línea, open() con with sería la opción correcta para no cargar todo en memoria.
Ejecuta: python 07_exportar.py. Abre exportaciones/resumen.txt y verifica el contenido.
TODO¶
Pista
Si una línea está vacía o no tiene coma, split(",") devolverá una lista con menos de dos elementos y lanzará ValueError al desempaquetar. Captura ese error dentro del bucle de líneas con try / except ValueError y usa continue para saltarla.
Comprobación¶
Salida esperada si no hay errores
Puedes añadir una sección así, justo después de Comprobación:Salida esperada si le quitamos permisos de lectura a notas_1b.txt.
Error leyendo notas_1b.txt: [Errno 13] Permission denied: '/.../datos/notas_1b.txt'
Resumen exportado a: resumen.txt
Resumen de notas — 2026-03-06
notas_1a: 7.88
Ana 8.5 (sin coma).
Aviso: línea ignorada en notas_1a.txt: 'Ana 8.5'
Resumen exportado a: resumen.txt
Resumen de notas — 2026-03-06
notas_1a: 7.60
notas_1b: 7.70
Salida esperada si todas las líneas fallan en un fichero, ese grupo no aparece en el resumen final.
Aviso: línea ignorada en notas_1b.txt: '---'
Aviso: línea ignorada en notas_1b.txt: ''
Resumen exportado a: resumen.txt
Resumen de notas — 2026-03-06
notas_1a: 7.88
Reflexión¶
Respuestas
Path.mkdir(exist_ok=True)garantiza que la carpeta de destino exista antes de escribir — siempre hazlo.try / exceptpor fichero dentro del bucle aísla los fallos: un fichero corrupto no detiene el proceso.continueen elexceptes el patrón habitual para procesar colecciones con tolerancia a fallos.write_text()es suficiente cuando el contenido se genera completo en memoria. Para ficheros grandes, usaopen().
Kata 1 — Rutas más expresivas
En 07_exportar.py, el nombre del grupo en el resumen viene de fichero.stem. Modifica el script para que el nombre sea la parte después del último guion bajo: notas_1a → 1A. Usa los métodos de string de Python.
Kata 2 — Log acumulativo
En 05_patron_completo.py usaste write_text() para el log, lo que sobreescribe en cada ejecución. Cámbialo para que añada líneas al log sin borrar las anteriores. Pista: abre con open(log, "a").
Kata 3 — Validación estricta (avanzado)
En 07_exportar.py, si una nota está fuera del rango [0, 10], considera la línea inválida y omítela del cálculo. Añade al resumen final cuántas líneas inválidas se encontraron en cada fichero.
Checklist de entrega¶
- He creado la estructura de directorios del proyecto usando
pathlibyPath(__file__).parent. - He inspeccionado rutas con
name,stem,suffix,parent,exists()eis_file(). - He capturado
FileNotFoundErroryPermissionErrorcon bloquesexceptseparados. - He aplicado el patrón completo
try / except / else / finallycon lógica de log. - He procesado varios ficheros con
glob()capturando errores por fichero concontinue. - He exportado un resumen usando
write_text()y he validado el fichero resultante. - He evitado concatenar rutas con strings usando el operador
/depathlib.
Actividades de ampliación¶
A — Leer con csv.reader (requiere paso 7)¶
El módulo csv de la librería estándar maneja correctamente nombres con comas, espacios y comillas.
Fichero: ampliacion_a.py
import csv
from pathlib import Path
BASE = Path(__file__).parent
ruta = BASE / "datos" / "notas_1a.txt"
with open(ruta, "r", encoding="utf-8", newline="") as f:
lector = csv.reader(f)
for fila in lector:
# tu código aquí — fila es una lista: ['Ana García', '8.5']
pass
¿Qué ocurre si el nombre de un alumno contiene una coma? ¿Cómo lo manejaría
csv.readerfrente a unsplit(",")?
B — Buscar alumno por nombre (requiere paso 6)¶
Implementa una función buscar_alumno(nombre) que busque un alumno en todos los ficheros de datos/ y devuelva una lista de tuplas (grupo, nota).
Fichero: ampliacion_b.py
from pathlib import Path
def buscar_alumno(nombre):
BASE = Path(__file__).parent
resultados = []
for fichero in sorted((BASE / "datos").glob("*.txt")):
# tu código aquí
pass
return resultados
print(buscar_alumno("Ana García"))
# Salida esperada: [('notas_1a', 8.5)]
C — Comparativa pathlib vs os.path (requiere paso 2)¶
Reescribe 02_rutas.py usando exclusivamente os.path en lugar de pathlib. Crea una tabla comparativa en un comentario al final del fichero con las equivalencias que hayas encontrado.
Fichero: ampliacion_c.py
import os.path
ruta = "datos/alumnos/notas_1a.txt"
# tu código aquí — replica lo que hace 02_rutas.py pero con os.path
# os.path.basename(), os.path.splitext(), os.path.dirname(), os.path.exists()...
# TABLA COMPARATIVA:
# | Operación | pathlib | os.path |
# |------------------|------------------|----------------------------|
# | Nombre fichero | ruta.name | os.path.basename(ruta) |
# | ... | ... | ... |
D — Historial de exportaciones (requiere paso 7)¶
Modifica 07_exportar.py para que el fichero de salida incluya un timestamp en el nombre: resumen_20260305_143022.txt. Así cada exportación genera un fichero nuevo sin sobreescribir el anterior.
from datetime import datetime
# tu código aquí
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
nombre_salida = f"resumen_{timestamp}.txt"
¿Cómo limpiarías exportaciones antiguas para no acumular ficheros indefinidamente?
E — Reto final: gestor interactivo de notas¶
Crea un programa gestor.py que presente un menú de texto en bucle con las siguientes opciones:
1. Listar grupos disponibles
2. Ver notas de un grupo
3. Añadir nota a un grupo
4. Exportar resumen general
5. Salir
Requisitos:
- Usa pathlib para todas las rutas.
- Captura errores de I/O en cada operación con mensajes claros.
- La opción 3 debe añadir la línea al fichero sin borrar las existentes.
- La opción 4 genera exportaciones/resumen_YYYYMMDD_HHMMSS.txt.
- No uses ninguna librería externa — solo la librería estándar de Python.