shutil.rmtree onexc: trate erros ao excluir pastas

Publicado em: 10/09/2026
Tempo de leitura: 6 minutos
Código Python para limpeza segura de diretórios com shutil.rmtree

Apagar uma árvore de diretórios parece uma tarefa simples até o momento em que surgem arquivos protegidos, permissões diferentes, links simbólicos, processos concorrentes ou falhas parciais. A função shutil.rmtree() resolve a remoção recursiva, enquanto o parâmetro onexc oferece um ponto central para tratar exceções de forma previsível. Esse recurso é útil em scripts de limpeza, testes automatizados, instaladores, pipelines de dados e rotinas que criam diretórios temporários.

Neste guia, você aprenderá como usar shutil.rmtree com onexc, como interpretar os argumentos recebidos pelo callback, quando tentar uma correção, quando registrar o erro e quando interromper a operação. O objetivo não é apenas apagar pastas, mas fazer isso com segurança, observabilidade e comportamento reproduzível.

O que shutil.rmtree faz

shutil.rmtree(caminho) remove um diretório e todo o seu conteúdo. Ao contrário de Path.rmdir() ou os.rmdir(), ele não exige que a pasta esteja vazia. Isso torna a função poderosa, mas também perigosa: um caminho incorreto pode apagar muitos arquivos de uma vez.

from shutil import rmtree

rmtree("build")

Antes de executar, valide o caminho, evite valores construídos diretamente com entrada do usuário e nunca aplique a função a diretórios críticos. Para manipulação moderna de caminhos, veja também pathlib.Path.walk no Python e os.fwalk no Python.

Por que usar onexc

Durante a remoção, diferentes operações internas podem falhar. Um arquivo pode estar somente leitura, um antivírus pode manter o recurso aberto, a permissão pode mudar entre a listagem e a exclusão ou o caminho pode desaparecer por ação de outro processo. O parâmetro onexc recebe um callback que é chamado quando uma exceção ocorre.

from shutil import rmtree

def ao_falhar(funcao, caminho, erro):
    print(f"Falha em {funcao.__name__}: {caminho}: {erro}")

rmtree("build", onexc=ao_falhar)

O callback recebe a função que falhou, o caminho envolvido e a própria exceção. Isso permite tomar decisões específicas sem espalhar blocos try/except pelo programa.

Diferença entre onexc e ignore_errors

ignore_errors=True ignora falhas durante a remoção. É uma opção simples, mas reduz a visibilidade. Você pode terminar a execução acreditando que tudo foi removido quando alguns arquivos ainda permanecem.

rmtree("cache", ignore_errors=True)

onexc é melhor quando você precisa registrar, corrigir ou classificar problemas. Em sistemas importantes, prefira decisões explícitas. Ignorar erros pode ser aceitável para uma limpeza não essencial, desde que o estado parcial não cause inconsistência.

Corrigindo arquivo somente leitura

Um caso clássico é a tentativa de remover um arquivo sem permissão de escrita. O callback pode alterar a permissão e repetir somente a função que falhou.

import os
import stat
from shutil import rmtree

def corrigir_permissao(funcao, caminho, erro):
    if isinstance(erro, PermissionError):
        os.chmod(caminho, stat.S_IWRITE)
        funcao(caminho)
        return
    raise erro

rmtree("resultado", onexc=corrigir_permissao)

Essa estratégia deve ser usada apenas em uma árvore que pertence à aplicação. Alterar permissões em caminhos arbitrários pode ampliar o impacto de um erro de configuração. O callback deve corrigir somente situações conhecidas e relançar exceções inesperadas.

Não esconda exceções desconhecidas

Um callback que apenas imprime a falha e retorna pode deixar a remoção incompleta. Em rotinas críticas, relance o erro quando não houver uma recuperação segura.

def tratar(funcao, caminho, erro):
    if isinstance(erro, FileNotFoundError):
        return
    raise erro

O FileNotFoundError pode ser tolerável quando outro processo já removeu o arquivo. Porém, uma falha de permissão, um erro de sistema de arquivos ou um caminho inválido pode indicar um problema maior. Para contratos explícitos em APIs, consulte dataclasses.KW_ONLY no Python.

Registrando contexto com logging

Em produção, substitua print() por logging estruturado. Inclua a função, o caminho, o tipo da exceção e o identificador da execução.

import logging
from shutil import rmtree

log = logging.getLogger(__name__)

def registrar(funcao, caminho, erro):
    log.error(
        "falha ao remover caminho",
        extra={
            "operacao": funcao.__name__,
            "caminho": caminho,
            "erro": type(erro).__name__,
        },
    )
    raise erro

rmtree("artefatos", onexc=registrar)

Logs ajudam a diferenciar uma falha temporária de uma configuração incorreta. Também permitem medir quantas operações de limpeza ficam incompletas.

Validando o caminho antes de apagar

Uma função segura deve resolver o caminho, comparar com uma raiz permitida e rejeitar diretórios críticos.

from pathlib import Path
from shutil import rmtree

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

def apagar_subdiretorio(valor):
    alvo = (RAIZ / valor).resolve()
    if alvo == RAIZ or RAIZ not in alvo.parents:
        raise ValueError("Caminho fora da raiz permitida")
    rmtree(alvo)

Essa checagem reduz riscos de sequências como ../. Também impede que um valor vazio resulte na remoção da própria raiz de trabalho. Para entender diretórios temporários, consulte tempfile no Python.

Operações recursivas exigem atenção a links simbólicos e mudanças concorrentes. A implementação moderna do Python usa proteções específicas em plataformas compatíveis, mas você ainda deve controlar a origem do caminho e limitar a operação a diretórios pertencentes ao processo.

Evite executar limpeza privilegiada em caminhos fornecidos por usuários. Se o processo roda com permissões elevadas, um erro pode atingir arquivos que normalmente estariam protegidos.

Retries com atraso curto

Em alguns sistemas, uma falha pode ser temporária porque outro processo ainda mantém o arquivo aberto. Uma política de tentativas deve ser pequena, limitada e observável.

import time
from shutil import rmtree

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

    def __call__(self, funcao, caminho, erro):
        if self.restantes and isinstance(erro, PermissionError):
            self.restantes -= 1
            time.sleep(0.2)
            funcao(caminho)
            return
        raise erro

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

Não transforme o callback em um laço infinito. Se a causa persistir, a execução deve falhar de forma clara.

Uso em testes automatizados

Testes frequentemente criam diretórios temporários e precisam limpá-los mesmo após uma asserção falhar. O ideal é usar gerenciadores de contexto e fixtures, deixando rmtree como mecanismo de limpeza controlado.

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

pasta = Path(mkdtemp())
try:
    (pasta / "dados.txt").write_text("teste", encoding="utf-8")
finally:
    rmtree(pasta)

Quando vários recursos precisam ser encerrados, contextlib.ExitStack no Python oferece uma estrutura flexível para registrar callbacks de limpeza.

Remoção idempotente

Uma rotina idempotente pode ser chamada mais de uma vez sem gerar um estado incorreto. Para isso, trate o caso em que o diretório já não existe.

from pathlib import Path
from shutil import rmtree

def remover_se_existir(caminho):
    alvo = Path(caminho)
    if alvo.exists():
        rmtree(alvo)

A verificação não elimina completamente condições de corrida, pois o estado pode mudar logo depois. Por isso, o callback ainda pode tolerar FileNotFoundError.

Compatibilidade de versões

onexc foi introduzido em versões modernas do Python como alternativa mais clara para tratamento de exceções. Verifique a versão mínima do seu projeto antes de usar o parâmetro. Bibliotecas distribuídas para ambientes antigos podem precisar de uma estratégia compatível ou de uma função auxiliar.

A documentação oficial de shutil.rmtree descreve a assinatura atual, os detalhes de segurança e o comportamento das exceções. A documentação de pathlib é uma referência complementar para validação de caminhos.

Função de limpeza segura

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

log = logging.getLogger(__name__)

def limpar(caminho, raiz):
    raiz = Path(raiz).resolve()
    alvo = Path(caminho).resolve()
    if alvo == raiz or raiz not in alvo.parents:
        raise ValueError("Alvo não permitido")

    def onexc(funcao, valor, erro):
        if isinstance(erro, FileNotFoundError):
            return
        if isinstance(erro, PermissionError):
            os.chmod(valor, stat.S_IWRITE)
            funcao(valor)
            return
        log.exception("limpeza incompleta", extra={"caminho": str(valor)})
        raise erro

    rmtree(alvo, onexc=onexc)

A função valida a raiz, tolera desaparecimento concorrente, tenta corrigir uma permissão conhecida e relança qualquer situação inesperada.

Boas práticas

Resolva o caminho antes da remoção. Limite a operação a uma raiz controlada. Nunca use entrada externa diretamente. Relance erros desconhecidos. Registre contexto suficiente. Evite tentativas infinitas. Teste o callback com arquivos protegidos e diretórios já removidos. Confirme a versão do Python. Não combine ignore_errors=True com uma expectativa de remoção completa.

Conclusão

shutil.rmtree é uma ferramenta direta para apagar árvores de diretórios, e onexc transforma falhas imprevisíveis em decisões explícitas. Com validação de caminho, logs, correções restritas e exceções relançadas, você consegue construir rotinas de limpeza muito mais confiáveis.

Comece com um callback simples que registra e relança. Depois, adicione apenas recuperações que você compreende e consegue testar. Dessa forma, a automação não esconde diretórios parcialmente removidos nem amplia o impacto de um caminho incorreto.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Conteúdo do artigo

    Artigos relacionados

    Gráfico de análise de dados para statistics.kde no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    statistics.kde: estime densidades no Python

    Aprenda statistics.kde no Python para estimar densidades, escolher bandwidth, comparar kernels e analisar distribuições com segurança.

    Ler mais

    Tempo de leitura: 8 minutos
    10/09/2026
    Código e estrutura de arquivos gerenciados com contextlib.ExitStack no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    contextlib.ExitStack: gerencie recursos dinâmicos

    Aprenda contextlib.ExitStack no Python para gerenciar vários recursos e callbacks com segurança, flexibilidade e código mais limpo.

    Ler mais

    Tempo de leitura: 5 minutos
    09/09/2026
    Desenvolvedor criando modelos de texto com string.Template no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    string.Template: templates simples e seguros

    Aprenda string.Template no Python para criar textos configuráveis, validar campos e substituir valores com segurança e clareza.

    Ler mais

    Tempo de leitura: 6 minutos
    09/09/2026
    Equipe sincronizada representando asyncio.Barrier no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    asyncio.Barrier: sincronize tarefas por etapas

    Aprenda asyncio.Barrier no Python para sincronizar tarefas em fases, evitar corridas e coordenar pipelines assíncronos com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Desenvolvedor criando modelos com dataclasses.KW_ONLY no Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    dataclasses.KW_ONLY: exija argumentos nomeados

    Aprenda dataclasses.KW_ONLY no Python para criar APIs com argumentos nomeados, evitar chamadas ambíguas e evoluir modelos com segurança.

    Ler mais

    Tempo de leitura: 5 minutos
    08/09/2026
    Desenvolvedor usando operator.methodcaller em código Python
    Python Avançado
    Foto de perfil de Leandro Hirt da Academify

    operator.methodcaller: chame métodos em pipelines

    Aprenda operator.methodcaller no Python para chamar métodos em map, sorted e pipelines com argumentos e código mais declarativo.

    Ler mais

    Tempo de leitura: 5 minutos
    07/09/2026