Percorrer árvores de diretórios é uma tarefa comum em automação, backup, auditoria, limpeza e indexação. Durante anos, os.walk() foi a ferramenta padrão. Versões modernas do Python também oferecem pathlib.Path.walk(), que integra a travessia ao modelo orientado a objetos de pathlib e devolve caminhos como objetos Path.
Neste guia, você aprenderá a percorrer diretórios de cima para baixo ou de baixo para cima, filtrar subpastas, tratar erros, lidar com links simbólicos, calcular tamanhos, localizar extensões, remover árvores com segurança e manter compatibilidade com versões anteriores.
Primeiro exemplo com Path.walk
from pathlib import Path
raiz = Path("projeto")
for diretorio, subdirs, arquivos in raiz.walk():
print("Diretório:", diretorio)
for nome in arquivos:
caminho = diretorio / nome
print(" Arquivo:", caminho)Cada iteração retorna uma tupla com o diretório atual como Path, uma lista de nomes de subdiretórios e uma lista de nomes de arquivos. Os nomes precisam ser combinados com o diretório para formar caminhos completos.
Por que usar pathlib?
Path concentra operações de caminho, extensão, leitura, escrita, comparação e metadados em uma API consistente.
for diretorio, _, arquivos in raiz.walk():
for nome in arquivos:
caminho = diretorio / nome
if caminho.suffix == ".py":
print(caminho.relative_to(raiz))Isso evita misturar repetidamente os.path.join(), os.path.splitext() e outras funções. O artigo sobre pathlib no Python apresenta os fundamentos da classe Path.
Travessia top-down
Por padrão, a árvore é percorrida de cima para baixo. O diretório pai aparece antes dos filhos.
for diretorio, subdirs, arquivos in raiz.walk(top_down=True):
print(diretorio)Esse modo permite modificar a lista subdirs para controlar quais pastas serão visitadas.
Ignorando diretórios
Remova nomes da lista de subdiretórios durante a travessia top-down.
IGNORAR = {".git", ".venv", "node_modules", "__pycache__"}
for diretorio, subdirs, arquivos in raiz.walk():
subdirs[:] = [nome for nome in subdirs if nome not in IGNORAR]
for nome in arquivos:
print(diretorio / nome)A atribuição com slice modifica a mesma lista usada internamente pelo walker. Apenas reatribuir subdirs = [...] não controla a recursão.
Ordenando a travessia
A ordem devolvida pelo sistema de arquivos não deve ser considerada estável. Para resultados reproduzíveis, ordene as listas.
for diretorio, subdirs, arquivos in raiz.walk():
subdirs.sort()
arquivos.sort()
for nome in arquivos:
print(diretorio / nome)A ordenação custa processamento adicional, mas é útil em testes, relatórios e geração determinística.
Travessia bottom-up
Com top_down=False, os filhos aparecem antes do pai.
for diretorio, subdirs, arquivos in raiz.walk(top_down=False):
print(diretorio)Esse modo é útil para remover uma árvore, pois os arquivos e diretórios internos precisam ser apagados antes do diretório pai.
Remoção segura de uma árvore
from pathlib import Path
def remover_arvore(raiz: Path) -> None:
for diretorio, subdirs, arquivos in raiz.walk(top_down=False):
for nome in arquivos:
(diretorio / nome).unlink()
for nome in subdirs:
(diretorio / nome).rmdir()
raiz.rmdir()Esse exemplo é destrutivo. Valide o caminho, recuse raízes perigosas e considere shutil.rmtree() para uma implementação consolidada. Nunca construa uma rotina de exclusão baseada em entrada não confiável sem normalização e limites.
Tratamento de erros com on_error
Erros ao listar um diretório podem ser tratados por uma função.
import logging
logger = logging.getLogger(__name__)
def registrar_erro(erro: OSError) -> None:
logger.warning("Não foi possível acessar %s: %s", erro.filename, erro)
for diretorio, subdirs, arquivos in raiz.walk(on_error=registrar_erro):
...Sem uma função, erros de listagem podem ser ignorados conforme o comportamento documentado. Quando a completude importa, registre, conte ou propague explicitamente.
Interrompendo ao primeiro erro
def falhar(erro: OSError) -> None:
raise erro
for item in raiz.walk(on_error=falhar):
...Escolha a política conforme o objetivo. Um indexador pode continuar e registrar; uma auditoria de segurança talvez precise falhar se qualquer área ficar inacessível.
Links simbólicos
Links simbólicos para diretórios exigem cuidado. Com a opção apropriada, o walker pode seguir esses links. Isso pode criar ciclos ou fazer a travessia sair da árvore esperada.
for diretorio, subdirs, arquivos in raiz.walk(follow_symlinks=False):
...O padrão mais seguro é não seguir links. Se o projeto precisa segui-los, mantenha um conjunto de diretórios reais visitados, imponha limites e considere dispositivos ou pontos de montagem.
Detectando ciclos
Uma estratégia em sistemas compatíveis é registrar combinações de dispositivo e inode obtidas por stat().
visitados: set[tuple[int, int]] = set()
for diretorio, subdirs, arquivos in raiz.walk(follow_symlinks=True):
info = diretorio.stat()
chave = (info.st_dev, info.st_ino)
if chave in visitados:
subdirs.clear()
continue
visitados.add(chave)Em plataformas diferentes, semânticas de inode e links podem variar. Teste no ambiente de destino.
Localizando arquivos por extensão
EXTENSOES = {".py", ".toml", ".json"}
encontrados: list[Path] = []
for diretorio, subdirs, arquivos in raiz.walk():
subdirs[:] = [d for d in subdirs if d not in {".git", ".venv"}]
for nome in arquivos:
caminho = diretorio / nome
if caminho.suffix.lower() in EXTENSOES:
encontrados.append(caminho)Para padrões simples sem necessidade de poda, Path.rglob() pode ser mais curto. walk() é melhor quando você precisa controlar diretórios, erros e ordem.
Calculando o tamanho da árvore
def tamanho_total(raiz: Path) -> int:
total = 0
for diretorio, _, arquivos in raiz.walk():
for nome in arquivos:
caminho = diretorio / nome
try:
total += caminho.stat().st_size
except OSError:
continue
return totalO resultado pode mudar durante a travessia se arquivos forem criados, removidos ou modificados. Para uma visão consistente, seria necessário um snapshot do sistema de arquivos ou mecanismo específico da plataforma.
Encontrando arquivos grandes
def maiores_que(raiz: Path, limite: int):
for diretorio, _, arquivos in raiz.walk():
for nome in arquivos:
caminho = diretorio / nome
try:
tamanho = caminho.stat().st_size
except OSError:
continue
if tamanho > limite:
yield caminho, tamanhoUsar um gerador evita manter todos os resultados na memória.
Comparação com os.walk
Path.walk() e os.walk() possuem conceitos semelhantes: top-down, poda de diretórios, tratamento de erros e links. A principal diferença prática é que Path.walk integra o diretório atual à API de pathlib.
import os
for diretorio, subdirs, arquivos in os.walk("projeto"):
...Em projetos que já usam Path, a nova API reduz conversões. Em bibliotecas compatíveis com versões antigas do Python, os.walk() continua sendo uma escolha sólida.
Compatibilidade de versão
Path.walk() foi adicionado em versões modernas do Python. Se o pacote suporta versões anteriores, teste a existência do método ou mantenha uma implementação com os.walk().
from pathlib import Path
import os
def percorrer(raiz: Path):
if hasattr(raiz, "walk"):
yield from raiz.walk()
return
for diretorio, subdirs, arquivos in os.walk(raiz):
yield Path(diretorio), subdirs, arquivosDeclare a versão mínima no pyproject.toml e nos metadados do pacote.
Concorrência durante a travessia
Árvores de diretórios mudam. Um arquivo listado pode desaparecer antes do stat(); permissões podem mudar e um diretório pode ser renomeado. Trate OSError próximo da operação que pode falhar.
Não presuma que verificar exists() antes evita erro. Entre a verificação e o uso, outro processo pode alterar o caminho. Prefira tentar a operação e capturar a exceção apropriada.
Segurança e caminhos não confiáveis
Ao percorrer uma raiz fornecida por usuário, resolva e valide a localização permitida. Links simbólicos e sequências como .. podem levar para fora do diretório esperado.
base = Path("/srv/uploads").resolve()
alvo = (base / entrada_usuario).resolve()
if not alvo.is_relative_to(base):
raise ValueError("caminho fora da área permitida")A validação precisa considerar a política de links simbólicos e possíveis mudanças concorrentes.
Erros comuns
- Reatribuir subdirs em vez de alterar a lista: use
subdirs[:]. - Confiar na ordem do sistema de arquivos: ordene quando precisar de determinismo.
- Seguir symlinks sem detectar ciclos: a travessia pode ser infinita.
- Ignorar erros silenciosamente: defina política de completude.
- Usar top-down para remover diretórios: prefira bottom-up.
- Presumir snapshot consistente: a árvore pode mudar durante o processo.
Exemplo completo: inventário de projeto
from dataclasses import dataclass
from pathlib import Path
@dataclass
class ArquivoInfo:
caminho: Path
tamanho: int
def inventariar(raiz: Path) -> list[ArquivoInfo]:
resultado: list[ArquivoInfo] = []
ignorar = {".git", ".venv", "node_modules", "__pycache__"}
for diretorio, subdirs, arquivos in raiz.walk():
subdirs[:] = sorted(d for d in subdirs if d not in ignorar)
for nome in sorted(arquivos):
caminho = diretorio / nome
try:
info = caminho.stat()
except OSError:
continue
resultado.append(
ArquivoInfo(
caminho=caminho.relative_to(raiz),
tamanho=info.st_size,
)
)
return resultadoO inventário ignora diretórios pesados, produz ordem estável, trabalha com caminhos relativos e tolera arquivos que desaparecem.
Conclusão
Path.walk() leva a travessia de árvores para a API de pathlib. Ele permite poda de diretórios, controle de ordem, tratamento de erros e travessia bottom-up usando objetos Path.
A documentação oficial de Path.walk no pathlib detalha parâmetros e comportamento com links. Use a função quando precisar de controle fino, trate mudanças concorrentes do sistema de arquivos e mantenha uma estratégia de compatibilidade se o projeto suporta versões antigas.







