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.
Links simbólicos e ataques de caminho
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.







