Recorrer árboles de directorios es habitual en automatización, backups, auditoría, limpieza e indexación. Durante años, os.walk() fue la herramienta estándar. Python moderno también ofrece pathlib.Path.walk(), que integra el recorrido con la API orientada a objetos de pathlib y devuelve el directorio actual como un objeto Path.
Esta guía cubre recorrido top-down y bottom-up, poda de carpetas, manejo de errores, enlaces simbólicos, tamaños, eliminación segura, orden determinista, cambios concurrentes y compatibilidad con versiones anteriores.
Primer ejemplo con Path.walk
from pathlib import Path
raiz = Path("proyecto")
for directorio, subdirs, archivos in raiz.walk():
print("Directorio:", directorio)
for nombre in archivos:
ruta = directorio / nombre
print(" Archivo:", ruta)Cada iteración devuelve el directorio actual como Path, una lista de nombres de subdirectorios y otra de nombres de archivos. Combina los nombres con el directorio para construir rutas completas.
Por qué usar pathlib
Path reúne unión de rutas, extensiones, lectura, escritura, metadatos y rutas relativas en una API consistente.
for directorio, _, archivos in raiz.walk():
for nombre in archivos:
ruta = directorio / nombre
if ruta.suffix == ".py":
print(ruta.relative_to(raiz))Esto evita mezclar repetidamente os.path.join(), os.path.splitext() y otras funciones. La guía de pathlib en Python explica el resto de la API.
Recorrido top-down
Por defecto, el directorio padre se entrega antes que sus hijos.
for directorio, subdirs, archivos in raiz.walk(top_down=True):
print(directorio)Este modo permite modificar la lista subdirs para decidir qué carpetas se visitarán.
Ignorar directorios
Elimina nombres de la lista de subdirectorios en el propio objeto.
IGNORAR = {".git", ".venv", "node_modules", "__pycache__"}
for directorio, subdirs, archivos in raiz.walk():
subdirs[:] = [nombre for nombre in subdirs if nombre not in IGNORAR]
for nombre in archivos:
print(directorio / nombre)La asignación por slice modifica la lista usada internamente por el walker. Escribir subdirs = [...] solo cambia una variable local y no poda la recursión.
Orden determinista
El orden del sistema de archivos no debe considerarse estable. Ordena las listas para resultados reproducibles.
for directorio, subdirs, archivos in raiz.walk():
subdirs.sort()
archivos.sort()
for nombre in archivos:
print(directorio / nombre)Ordenar añade trabajo, pero resulta útil en tests, informes y generación determinista.
Recorrido bottom-up
Con top_down=False, los hijos aparecen antes que el padre.
for directorio, subdirs, archivos in raiz.walk(top_down=False):
print(directorio)Es apropiado para eliminar árboles porque los archivos y carpetas internas deben borrarse antes que el directorio padre.
Eliminar un árbol con seguridad
from pathlib import Path
def eliminar_arbol(raiz: Path) -> None:
for directorio, subdirs, archivos in raiz.walk(top_down=False):
for nombre in archivos:
(directorio / nombre).unlink()
for nombre in subdirs:
(directorio / nombre).rmdir()
raiz.rmdir()El ejemplo es destructivo. Valida la raíz, rechaza ubicaciones peligrosas y considera shutil.rmtree() para una implementación consolidada. No elimines rutas formadas desde entrada no confiable sin límites estrictos.
Manejo de errores con on_error
Los errores al listar un directorio pueden enviarse a una función.
import logging
logger = logging.getLogger(__name__)
def registrar_error(error: OSError) -> None:
logger.warning("No se pudo acceder a %s: %s", error.filename, error)
for directorio, subdirs, archivos in raiz.walk(on_error=registrar_error):
...Cuando la completitud importa, registra, cuenta o propaga los fallos en vez de omitir áreas inaccesibles sin explicación.
Fallar en el primer error
def fallar(error: OSError) -> None:
raise error
for elemento in raiz.walk(on_error=fallar):
...Un indexador puede continuar con warnings, mientras una auditoría quizá deba fallar si no puede inspeccionar una zona.
Enlaces simbólicos
Los symlinks a directorios requieren cuidado. Seguirlos puede crear ciclos o salir del árbol esperado.
for directorio, subdirs, archivos in raiz.walk(follow_symlinks=False):
...No seguir enlaces es el valor más seguro. Si debes seguirlos, registra directorios reales visitados e impone límites.
Detectar ciclos
En sistemas compatibles, un conjunto de pares dispositivo e inode puede detectar repeticiones.
visitados: set[tuple[int, int]] = set()
for directorio, subdirs, archivos in raiz.walk(follow_symlinks=True):
info = directorio.stat()
clave = (info.st_dev, info.st_ino)
if clave in visitados:
subdirs.clear()
continue
visitados.add(clave)La semántica de inode y enlaces cambia entre plataformas, sistemas de red y puntos de montaje. Prueba en el entorno real.
Buscar archivos por extensión
EXTENSIONES = {".py", ".toml", ".json"}
encontrados: list[Path] = []
for directorio, subdirs, archivos in raiz.walk():
subdirs[:] = [d for d in subdirs if d not in {".git", ".venv"}]
for nombre in archivos:
ruta = directorio / nombre
if ruta.suffix.lower() in EXTENSIONES:
encontrados.append(ruta)Para un patrón recursivo simple sin poda, Path.rglob() puede ser más corto. walk() es mejor cuando importan directorios, errores y orden.
Calcular el tamaño del árbol
def tamano_total(raiz: Path) -> int:
total = 0
for directorio, _, archivos in raiz.walk():
for nombre in archivos:
ruta = directorio / nombre
try:
total += ruta.stat().st_size
except OSError:
continue
return totalEl resultado no es un snapshot. Los archivos pueden cambiar, aparecer o desaparecer durante el recorrido.
Generar archivos grandes
def mayores_que(raiz: Path, limite: int):
for directorio, _, archivos in raiz.walk():
for nombre in archivos:
ruta = directorio / nombre
try:
tamano = ruta.stat().st_size
except OSError:
continue
if tamano > limite:
yield ruta, tamanoUn generador evita mantener todos los resultados en memoria.
Path.walk frente a os.walk
Los conceptos son parecidos: top-down, poda, callbacks de error y enlaces. La diferencia práctica es el tipo del directorio actual y la integración con pathlib.
import os
for directorio, subdirs, archivos in os.walk("proyecto"):
...Los proyectos que ya usan Path pueden preferir Path.walk. Las bibliotecas compatibles con versiones antiguas pueden seguir con os.walk().
Compatibilidad de versión
Path.walk() se añadió en Python moderno. Un paquete que soporte versiones anteriores puede ofrecer un fallback.
from pathlib import Path
import os
def recorrer_compatible(raiz: Path):
if hasattr(raiz, "walk"):
yield from raiz.walk()
return
for directorio, subdirs, archivos in os.walk(raiz):
yield Path(directorio), subdirs, archivosDeclara la versión mínima en pyproject.toml y en los metadatos del paquete.
Cambios concurrentes del sistema de archivos
Los árboles cambian mientras se inspeccionan. Un archivo listado puede desaparecer antes de stat(); los permisos pueden cambiar y una carpeta puede renombrarse. Captura OSError cerca de la operación que puede fallar.
Comprobar exists() no elimina la carrera. Otro proceso puede modificar la ruta entre la comprobación y el uso.
Rutas no confiables y seguridad
Resuelve y valida una raíz proporcionada por usuario contra una base permitida. Los symlinks y segmentos .. pueden escapar del área prevista.
base = Path("/srv/uploads").resolve()
objetivo = (base / entrada_usuario).resolve()
if not objetivo.is_relative_to(base):
raise ValueError("ruta fuera del área permitida")La política también debe considerar enlaces simbólicos y reemplazos concurrentes.
Errores comunes
- Reasignar subdirs en vez de modificar la lista: usa asignación por slice.
- Confiar en el orden del filesystem: ordena cuando importe el determinismo.
- Seguir symlinks sin detectar ciclos: el recorrido puede no terminar.
- Ignorar errores sin política: la completitud puede ser engañosa.
- Eliminar top-down: usa bottom-up.
- Suponer un snapshot consistente: el árbol cambia durante el proceso.
Ejemplo completo: inventario de proyecto
from dataclasses import dataclass
from pathlib import Path
@dataclass
class ArchivoInfo:
ruta: Path
tamano: int
def inventariar(raiz: Path) -> list[ArchivoInfo]:
resultado: list[ArchivoInfo] = []
ignorar = {".git", ".venv", "node_modules", "__pycache__"}
for directorio, subdirs, archivos in raiz.walk():
subdirs[:] = sorted(d for d in subdirs if d not in ignorar)
for nombre in sorted(archivos):
ruta = directorio / nombre
try:
info = ruta.stat()
except OSError:
continue
resultado.append(
ArchivoInfo(
ruta=ruta.relative_to(raiz),
tamano=info.st_size,
)
)
return resultadoEl inventario ignora carpetas pesadas, produce orden estable, guarda rutas relativas y tolera archivos que desaparecen.
Conclusión
Path.walk() lleva el recorrido de árboles a pathlib. Permite poda, orden, errores, travesía bottom-up y operaciones basadas en Path.
La documentación oficial de Path.walk en Python explica parámetros y enlaces. Úsalo cuando necesites control fino, maneja cambios concurrentes y conserva un fallback si soportas versiones antiguas.







