os.fwalk es una función de la biblioteca estándar de Python que recorre árboles de directorios. Se parece a os.walk, pero cada iteración también entrega un descriptor de archivo para el directorio actual. Ese descriptor puede utilizarse con el parámetro dir_fd de funciones como os.stat, os.open, os.unlink y os.rename.
Para scripts comunes, os.walk o pathlib suelen ser opciones más simples. os.fwalk resulta especialmente útil cuando el programa realiza operaciones sensibles sobre una estructura que puede cambiar mientras se procesa. Es apropiado para herramientas administrativas, limpiadores, inventarios, scanners, empaquetadores y servicios que manipulan muchos archivos.
Uso básico
import os
for raiz, directorios, archivos, fd_raiz in os.fwalk("datos"):
print(raiz, fd_raiz)
for nombre in archivos:
print("archivo:", nombre)
Cada paso devuelve la ruta actual, una lista modificable de subdirectorios, una lista de archivos y el descriptor del directorio actual. Los nombres pueden resolverse de forma relativa a fd_raiz.
Por qué usar descriptores de directorio
Un patrón habitual construye una ruta completa, verifica el archivo y después ejecuta otra operación sobre esa ruta. Entre ambas acciones, otro proceso puede sustituir una parte del camino. Esta situación se conoce como condición de carrera TOCTOU. Las operaciones relativas a un descriptor no eliminan todos los riesgos, pero reducen la necesidad de volver a resolver la ruta completa.
import os
for raiz, _, archivos, fd_raiz in os.fwalk("temporales"):
for nombre in archivos:
info = os.stat(nombre, dir_fd=fd_raiz, follow_symlinks=False)
if info.st_size == 0:
os.unlink(nombre, dir_fd=fd_raiz)
El ejemplo elimina archivos vacíos sin reconstruir la ruta completa para cada consulta y eliminación.
El descriptor es temporal
El descriptor devuelto por os.fwalk solo permanece válido hasta el siguiente paso de la iteración. No debe almacenarse para usarlo después. Si necesitas conservarlo, duplícalo con os.dup y cierra la copia al terminar.
import os
for raiz, _, _, fd_raiz in os.fwalk("datos"):
copia = os.dup(fd_raiz)
try:
print(os.listdir(copia))
finally:
os.close(copia)
Las fugas de descriptores pueden agotar el límite del proceso, por lo que el bloque try/finally es fundamental.
Recorrido de arriba hacia abajo
El valor predeterminado es topdown=True. El directorio padre aparece antes que sus hijos y la lista directorios puede modificarse para podar la búsqueda.
import os
for raiz, directorios, archivos, fd_raiz in os.fwalk("proyecto", topdown=True):
directorios[:] = [d for d in directorios if d not in {".git", ".venv", "__pycache__"}]
print(raiz, len(archivos))
La poda evita llamadas innecesarias al sistema y excluye árboles irrelevantes o costosos.
Recorrido de abajo hacia arriba
Con topdown=False, los hijos se visitan antes que el padre. Este modo es útil para eliminar una estructura porque primero deben borrarse los archivos y subdirectorios.
import os
base = "salida-antigua"
for raiz, directorios, archivos, fd_raiz in os.fwalk(base, topdown=False):
for nombre in archivos:
os.unlink(nombre, dir_fd=fd_raiz)
for nombre in directorios:
os.rmdir(nombre, dir_fd=fd_raiz)
os.rmdir(base)
El código destructivo debe validar la raíz, gestionar permisos y fallos parciales y nunca aceptar directamente una ruta no confiable. Conviene ofrecer un modo de simulación y un registro de auditoría.
Enlaces simbólicos
follow_symlinks vale False por defecto. Esta configuración evita salir accidentalmente del árbol esperado y reduce el riesgo de ciclos. Si necesitas seguir enlaces, registra los directorios visitados mediante dispositivo e inode.
La política también debe ser explícita en las consultas de metadatos. Con follow_symlinks=False, os.stat inspecciona el propio enlace en lugar de su destino.
Gestión de errores
Los archivos pueden desaparecer, los permisos pueden cambiar y el almacenamiento remoto puede fallar durante el recorrido. Usa onerror para errores al leer directorios y captura excepciones específicas para cada entrada.
import os
def registrar(error):
print(f"No se pudo acceder a {error.filename}: {error}")
for raiz, _, archivos, fd_raiz in os.fwalk("datos", onerror=registrar):
for nombre in archivos:
try:
info = os.stat(nombre, dir_fd=fd_raiz, follow_symlinks=False)
except FileNotFoundError:
continue
except PermissionError as error:
registrar(error)
else:
print(raiz, nombre, info.st_size)
En un directorio activo, que un archivo desaparezca puede ser un evento normal y no un fallo fatal.
Comparación con os.walk y pathlib
pathlib.Path.rglob ofrece una API orientada a objetos muy clara para automatizaciones comunes. os.walk es conocido y portable. os.fwalk debe elegirse cuando las llamadas relativas a descriptores son una parte importante del diseño. Usarlo sin esa necesidad añade complejidad.
La portabilidad también importa. No todas las plataformas admiten todos los argumentos dir_fd. Consulta os.supports_dir_fd y prueba en los sistemas operativos de destino.
import os
if os.stat in os.supports_dir_fd:
print("os.stat admite dir_fd")
Crear un inventario
import os
from dataclasses import dataclass
@dataclass
class Elemento:
ruta: str
tamano: int
modo: int
def inventario(base: str) -> list[Elemento]:
resultado = []
for raiz, directorios, archivos, fd_raiz in os.fwalk(base, follow_symlinks=False):
directorios[:] = [d for d in directorios if d != ".git"]
for nombre in archivos:
try:
info = os.stat(nombre, dir_fd=fd_raiz, follow_symlinks=False)
except (FileNotFoundError, PermissionError):
continue
resultado.append(Elemento(os.path.join(raiz, nombre), info.st_size, info.st_mode))
return resultado
Para árboles enormes, convierte la función en generador en vez de acumular todos los elementos. Envía cada resultado a una base de datos, una cola o un escritor de informes.
Renombrar con descriptores
import os
for raiz, _, archivos, fd_raiz in os.fwalk("entradas"):
for nombre in archivos:
if nombre.endswith(".part"):
definitivo = nombre.removesuffix(".part")
os.rename(nombre, definitivo, src_dir_fd=fd_raiz, dst_dir_fd=fd_raiz)
Los dos nombres se resuelven dentro del mismo directorio abierto. En producción debes prevenir colisiones y definir cómo recuperar transferencias interrumpidas.
Rendimiento
os.fwalk no elimina la latencia del sistema de archivos. Cada llamada a stat sigue llegando al sistema operativo. Poda pronto, evita metadatos innecesarios y presta especial atención al almacenamiento de red. El número de llamadas suele importar más que las microoptimizaciones en Python.
Un paralelismo ilimitado puede saturar discos y servidores. Limita los workers y diseña tareas idempotentes que puedan repetirse con seguridad.
Estrategias de prueba
Las pruebas deben crear árboles temporales con archivos normales, directorios anidados, archivos vacíos, entradas sin permiso cuando la plataforma lo permita y enlaces simbólicos. Verifica la poda, la eliminación bottom-up, el tratamiento de errores y el cierre de descriptores. Nunca ejecutes pruebas destructivas sobre directorios reales.
Buenas prácticas
Valida la raíz, no sigas enlaces por defecto, duplica descriptores solo cuando sea necesario, cierra cada copia, trata la desaparición transitoria como un evento esperado y registra las acciones destructivas. Prefiere una API más simple cuando dir_fd no aporte una ventaja concreta.
Consulta también las guías de Academify sobre pathlib en Python, el módulo os, shutil y contextlib. Las referencias externas principales son la documentación oficial de os.fwalk y la sección de archivos y directorios.
Conclusión
os.fwalk combina el recorrido recursivo con un descriptor para cada directorio actual. Permite operaciones relativas más precisas, reduce algunas carreras relacionadas con rutas y ofrece mayor control sobre árboles que cambian. No sustituye universalmente a os.walk, pero es una herramienta valiosa cuando la manipulación basada en descriptores mejora la seguridad y la robustez.







