shutil.rmtree onexc: maneja errores al borrar carpetas

Publicado el: 10/09/2026
Tempo de leitura: 7 minutos
Código Python para limpieza segura de directorios con shutil.rmtree

Eliminar un árbol de directorios parece sencillo hasta que aparecen archivos protegidos, permisos cambiantes, enlaces simbólicos, procesos concurrentes o fallos parciales. shutil.rmtree() realiza la eliminación recursiva y el parámetro onexc ofrece un punto central para tratar excepciones de forma previsible. Es útil en scripts de limpieza, pruebas automatizadas, instaladores, pipelines de datos, despliegues y aplicaciones que crean espacios de trabajo temporales.

Esta guía explica cómo usar shutil.rmtree con onexc, cómo interpretar los argumentos del callback, cuándo conviene reintentar, cuándo registrar un error y cuándo detener la operación. El objetivo no es solamente borrar carpetas, sino hacerlo con seguridad, observabilidad y un comportamiento reproducible.

Qué hace shutil.rmtree

shutil.rmtree(ruta) elimina un directorio y todo su contenido. A diferencia de Path.rmdir() u os.rmdir(), no exige que la carpeta esté vacía. Esa capacidad es cómoda, pero también peligrosa: una ruta incorrecta puede borrar una gran cantidad de datos.

from shutil import rmtree

rmtree("build")

Antes de ejecutarla, valida el destino, evita rutas construidas directamente con entradas externas y no apliques eliminación recursiva a directorios críticos. Para recorridos modernos, consulta pathlib.Path.walk en Python y os.fwalk en Python.

Por qué onexc es importante

Durante la eliminación pueden fallar varias operaciones internas. Un archivo puede ser de solo lectura, un antivirus puede mantenerlo abierto, los permisos pueden cambiar entre la lista y el borrado o otro proceso puede eliminarlo primero. onexc recibe un callback cuando ocurre una excepción.

from shutil import rmtree

def al_fallar(funcion, ruta, error):
    print(f"Fallo en {funcion.__name__}: {ruta}: {error}")

rmtree("build", onexc=al_fallar)

El callback recibe la función que falló, la ruta implicada y el objeto de excepción. Esto permite tomar decisiones específicas sin distribuir bloques amplios de try/except por toda la aplicación.

onexc frente a ignore_errors

ignore_errors=True suprime los fallos durante la eliminación. Es sencillo, pero reduce la visibilidad. El programa puede terminar aunque todavía existan archivos y los pasos siguientes pueden asumir un estado limpio que no es real.

rmtree("cache", ignore_errors=True)

onexc es mejor cuando necesitas registrar, reparar, clasificar o relanzar fallos. Ignorar errores puede ser aceptable en una limpieza descartable sin consecuencias, pero no debería ser la opción predeterminada en flujos importantes.

Corregir un archivo de solo lectura

Un caso común es un archivo que no puede eliminarse por sus permisos. El callback puede cambiar el permiso y repetir únicamente la operación que falló.

import os
import stat
from shutil import rmtree

def corregir_permiso(funcion, ruta, error):
    if isinstance(error, PermissionError):
        os.chmod(ruta, stat.S_IWRITE)
        funcion(ruta)
        return
    raise error

rmtree("salida", onexc=corregir_permiso)

Esta estrategia debe limitarse a un árbol propiedad de la aplicación. Cambiar permisos en destinos arbitrarios puede ampliar el impacto de una configuración equivocada. El callback debe reparar solo situaciones conocidas y relanzar las demás.

No ocultes excepciones desconocidas

Un callback que solo imprime y retorna puede dejar el árbol parcialmente eliminado. En limpiezas críticas, relanza los errores que no puedan corregirse con seguridad.

def tratar(funcion, ruta, error):
    if isinstance(error, FileNotFoundError):
        return
    raise error

FileNotFoundError puede ser inofensivo cuando otro worker ya eliminó el elemento. Un fallo de permisos, un error del sistema de archivos o una ruta inválida pueden indicar un problema mayor. Para contratos explícitos, revisa dataclasses.KW_ONLY en Python.

Registrar contexto útil

En producción conviene reemplazar print() por logging estructurado. Incluye la función, la ruta, el tipo de excepción y el identificador de la ejecución.

import logging
from shutil import rmtree

log = logging.getLogger(__name__)

def registrar(funcion, ruta, error):
    log.error(
        "fallo al eliminar ruta",
        extra={
            "operacion": funcion.__name__,
            "ruta": ruta,
            "tipo_error": type(error).__name__,
        },
    )
    raise error

rmtree("artefactos", onexc=registrar)

Los logs permiten distinguir un bloqueo temporal de un problema permanente de configuración y medir cuántas limpiezas quedan incompletas.

Validar la ruta antes de borrar

Una función segura debe resolver la ruta, compararla con una raíz permitida y rechazar directorios críticos.

from pathlib import Path
from shutil import rmtree

RAIZ = Path("/srv/mi-app/work").resolve()

def borrar_subdirectorio(valor):
    destino = (RAIZ / valor).resolve()
    if destino == RAIZ or RAIZ not in destino.parents:
        raise ValueError("Ruta fuera de la raíz permitida")
    rmtree(destino)

La comprobación bloquea recorridos como ../ y evita que un valor vacío seleccione la propia raíz. Para espacios temporales, consulta tempfile en Python.

Enlaces simbólicos y ataques de ruta

Las operaciones recursivas requieren atención a enlaces simbólicos y condiciones de carrera. Las implementaciones modernas de Python utilizan protecciones adicionales en plataformas compatibles, pero la aplicación aún debe controlar el origen de los destinos y limitar la limpieza a directorios propios.

No ejecutes una limpieza privilegiada sobre rutas proporcionadas por usuarios. Cuando el proceso tiene permisos elevados, un error de validación puede alcanzar archivos que normalmente estarían protegidos.

Reintentos cortos y limitados

Algunos fallos son temporales porque otro proceso mantiene el archivo abierto. Una política de reintentos debe ser pequeña, limitada y observable.

import time
from shutil import rmtree

class Tratador:
    def __init__(self, intentos=2):
        self.restantes = intentos

    def __call__(self, funcion, ruta, error):
        if self.restantes and isinstance(error, PermissionError):
            self.restantes -= 1
            time.sleep(0.2)
            funcion(ruta)
            return
        raise error

rmtree("cache", onexc=Tratador())

No conviertas el callback en un bucle infinito. Si la condición persiste, falla claramente y conserva contexto suficiente para el diagnóstico.

Uso en pruebas automatizadas

Las pruebas crean directorios temporales y deben eliminarlos incluso cuando una aserción falla. Es preferible usar context managers y fixtures, dejando rmtree como mecanismo controlado.

from pathlib import Path
from tempfile import mkdtemp
from shutil import rmtree

carpeta = Path(mkdtemp())
try:
    (carpeta / "datos.txt").write_text("prueba", encoding="utf-8")
finally:
    rmtree(carpeta)

Cuando varios recursos deben cerrarse dinámicamente, contextlib.ExitStack en Python permite registrar callbacks de limpieza con flexibilidad.

Limpieza idempotente

Una rutina idempotente puede ejecutarse más de una vez sin producir un estado inválido. Una estrategia básica consiste en aceptar que el directorio ya no exista.

from pathlib import Path
from shutil import rmtree

def eliminar_si_existe(ruta):
    destino = Path(ruta)
    if destino.exists():
        rmtree(destino)

La verificación previa no elimina las carreras porque el estado puede cambiar inmediatamente después. El callback todavía puede tolerar FileNotFoundError si ese resultado es válido.

Callbacks fáciles de probar

Mantén el callback pequeño y determinista. Separa la política de los efectos secundarios. Una función puede decidir si una excepción es recuperable, otra puede reparar y otra registrar el fallo final. Así las pruebas unitarias son más simples y el callback no se transforma en un manejador desordenado.

def recuperable(error):
    return isinstance(error, (FileNotFoundError, PermissionError))

Las pruebas deben cubrir un archivo ausente, un archivo protegido, una excepción inesperada y un reintento que también falla. Confirma que los errores desconocidos no sean silenciados.

Compatibilidad de versiones

onexc está disponible en versiones modernas de Python como callback más claro para las excepciones de rmtree. Comprueba la versión mínima del proyecto. Una biblioteca compatible con entornos antiguos puede necesitar una capa de compatibilidad.

La documentación oficial de shutil.rmtree explica la firma actual, los detalles de seguridad y el comportamiento de las excepciones. La documentación de pathlib complementa la validación de rutas.

Una función de limpieza más segura

import logging
import os
import stat
from pathlib import Path
from shutil import rmtree

log = logging.getLogger(__name__)

def limpiar(ruta, raiz):
    raiz = Path(raiz).resolve()
    destino = Path(ruta).resolve()
    if destino == raiz or raiz not in destino.parents:
        raise ValueError("Destino no permitido")

    def onexc(funcion, valor, error):
        if isinstance(error, FileNotFoundError):
            return
        if isinstance(error, PermissionError):
            os.chmod(valor, stat.S_IWRITE)
            funcion(valor)
            return
        log.exception("limpieza incompleta", extra={"ruta": str(valor)})
        raise error

    rmtree(destino, onexc=onexc)

La función valida la raíz permitida, tolera la desaparición concurrente, repara un caso conocido de permisos y relanza cualquier situación inesperada.

Salvaguardas operativas

Considera un modo de simulación que enumere los destinos sin eliminarlos. En tareas programadas, registra el número de archivos antes y después. Para datos valiosos, mueve primero el directorio a una zona de cuarentena y elimínalo después de una validación. Estas prácticas reducen el impacto de un error y ofrecen una oportunidad de recuperación.

No uses la eliminación recursiva como sustituto de una política de retención. Define qué directorios pueden borrarse, qué antigüedad deben tener y qué proceso es su propietario.

Buenas prácticas

Resuelve la ruta antes de borrar. Limita la operación a una raíz controlada. No pases entradas externas directamente. Relanza errores desconocidos. Registra contexto suficiente. Evita reintentos infinitos. Prueba archivos protegidos y directorios ya eliminados. Confirma la versión de Python. No combines ignore_errors=True con una expectativa de eliminación completa.

Conclusión

shutil.rmtree es una herramienta directa para eliminar árboles de directorios y onexc convierte fallos imprevisibles en decisiones explícitas. Con validación de rutas, logs, reparaciones limitadas y excepciones relanzadas, la limpieza es mucho más confiable.

Comienza con un callback que registre y relance. Añade solamente recuperaciones que comprendas y puedas probar. Así la automatización no oculta árboles parcialmente eliminados ni amplía el impacto de una ruta incorrecta.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Gráfico de análisis de datos para statistics.kde en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    statistics.kde: estima densidades en Python

    Aprende statistics.kde en Python para estimar densidades, elegir bandwidth, comparar kernels e interpretar distribuciones con cuidado.

    Ler mais

    Tempo de leitura: 7 minutos
    10/09/2026
    Codigo y archivos gestionados con ExitStack en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextlib.ExitStack: gestiona recursos dinámicos

    Aprende contextlib.ExitStack en Python para gestionar recursos dinámicos, callbacks de limpieza y excepciones de forma segura.

    Ler mais

    Tempo de leitura: 4 minutos
    09/09/2026
    Desarrollador creando plantillas de texto con string.Template en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    string.Template: plantillas de texto simples y seguras

    Aprende string.Template en Python para crear textos configurables, validar campos y sustituir valores de forma clara y segura.

    Ler mais

    Tempo de leitura: 6 minutos
    09/09/2026
    Equipo sincronizado representando asyncio.Barrier en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Barrier: sincroniza tareas por fases

    Aprende asyncio.Barrier en Python para sincronizar tareas por fases, coordinar pipelines y gestionar cancelaciones con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    08/09/2026
    Desarrollador creando modelos con dataclasses.KW_ONLY en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    dataclasses.KW_ONLY: exige argumentos con nombre

    Aprende dataclasses.KW_ONLY en Python para exigir argumentos con nombre, evitar llamadas ambiguas y evolucionar APIs con seguridad.

    Ler mais

    Tempo de leitura: 6 minutos
    08/09/2026
    Desarrollador usando operator.methodcaller en código Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    operator.methodcaller: llama métodos en pipelines

    Aprende operator.methodcaller en Python para map, sorted, callbacks, argumentos y pipelines declarativos claros y reutilizables.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026