Automação de Arquivos e Diretórios
pathlib e os módulos shutil/tempfile substituem comandos de shell frágeis para copiar, rotacionar e gerar arquivos. Escritas seguras e codificação explícita evitam perda de dados quando scripts são executados automaticamente via cron.
Receita
from pathlib import Path
import tempfile
import os
def atomic_write(path: Path, data: str) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=path.parent, text=True)
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
f.write(data)
os.replace(tmp, path)
except Exception:
os.unlink(tmp)
raiseQuando usar isso:
- Transformações de arquivos em lote (renomear, compactar, validar)
- Geração de relatórios para diretórios conhecidos
- Tarefas de rotação e limpeza de logs
- Sincronização de artefatos entre estágios de build em um mesmo host
Exemplo de Trabalho
Encontra arquivos CSV, valida se não estão vazios, escreve atomicamente um manifesto de resumo.
from __future__ import annotations
import json
import os
import tempfile
from dataclasses import dataclass
from pathlib import Path
@dataclass(frozen=True)
class FileRecord:
path: Path
size: int
def discover_csv(root: Path) -> list[FileRecord]:
records: list[FileRecord] = []
for path in root.glob("**/*.csv"):
if path.is_file():
size = path.stat().st_size
if size == 0:
raise ValueError(f"csv vazio: {path}")
records.append(FileRecord(path, size))
return records
def atomic_write_json(path: Path, payload: dict) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, tmp = tempfile.mkstemp(dir=path.parent, suffix=".tmp")
try:
with os.fdopen(fd, "w", encoding="utf-8") as f:
json.dump(payload, f, indent=2)
os.replace(tmp, path)
except Exception:
os.unlink(tmp)
raise
def main(root: Path, manifest: Path) -> int:
files = discover_csv(root)
atomic_write_json(
manifest,
{"count": len(files), "files": [{"path": str(r.path), "size": r.size} for r in files]},
)
return 0
if __name__ == "__main__":
raise SystemExit(main(Path("data/incoming"), Path("data/manifest.json")))O que isso demonstra:
- Descoberta recursiva
glob("**/*.csv")compathlib - Verificação de arquivo vazio falha rapidamente antes de publicar o manifesto
- Renomeação atômica
os.replaceno mesmo sistema de arquivos evita leituras parciais
Mergulho Profundo
Essenciais do pathlib
| Operação | API |
|---|---|
| Junção | base / "child" |
| Existe | path.exists(), is_file() |
| Leitura | read_text(encoding="utf-8") |
| Travessia de árvore | path.rglob("*") |
Padrão de Escrita Segura
- Escrever em um arquivo temporário no diretório de destino (mesmo ponto de montagem)
fsyncopcional para durabilidade críticaos.replacesubstitui atomicamente no lugar
Notas de Python
import shutil
from pathlib import Path
shutil.copy2(Path("src.txt"), Path("backup/src.txt")) # preserva metadados
shutil.rmtree(Path("tmp/build"), ignore_errors=False)Armadilhas
- Escrever diretamente no caminho final - leitores veem arquivos parcialmente escritos. Correção: temp atômico + replace.
- Codificação padrão no Windows -
mojibakeem CSV UTF-8. Correção: sempreencoding="utf-8"explicitamente. - Seguir links simbólicos em exclusões - remove alvos inesperados. Correção:
is_symlink()verifica antes deunlink. - Corrida no
mkdir- scripts paralelos colidem. Correção:exist_ok=Trueou capturarFileExistsError. - Árvores enormes em memória -
list(rglob)em milhões de arquivos. Correção: iterar o gerador sem materializar todos os caminhos.
Alternativas
| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
os.walk | Manutenção de código legado | Código novo - preferir pathlib |
Shell find | Comando único em caixa de operações | Necessidade de automação Python multiplataforma |
watchdog | Reagir a eventos do FS | Tarefa noturna é suficiente |
FAQs
pathlib vs os.path?
pathlib é o padrão moderno em código Python 3.14 - mais claro e encadeável.
Como copio árvores?
shutil.copytree(src, dst) com dirs_exist_ok=True no 3.14 para mesclagens.
Como escolho a codificação?
UTF-8 para texto; open(path, "rb") para binário - nunca assuma a codificação local.
Escrita atômica entre sistemas de arquivos?
os.replace requer o mesmo ponto de montagem - use nome único + versionamento no nível do aplicativo se for entre dispositivos.
Como excluo diretórios?
Filtrar path.parts ou usar fnmatch na string de caminho relativo durante a travessia.
Padrões de exclusão segura?
Mover para um diretório de staging primeiro; --dry-run lista os alvos antes de unlink.
Como funcionam as permissões?
path.chmod(0o640) após a escrita para arquivos secretos; respeitar umask em diretórios compartilhados.
Escritores concorrentes?
Bloqueio de arquivo (fcntl/portalocker) ou delegar para um worker de fila para garantia de escritor único.
Como eu testo?
Fixture tmp_path do pytest para diretório isolado por teste.
E quanto ao S3 em vez de disco local?
Use boto3 para armazenamento de objetos; pathlib local para agentes de build e processamento de logs em disco.
Relacionado
- Noções Básicas de Scripting - Estrutura de CLI
- Scripts Robustos - dry-run e idempotência
- Análise de Logs e Texto - ler arquivos de log grandes
- subprocess & Interoperação com Shell - tar/rsync externos
- Melhores Práticas de Scripting de Sistema - regras para scripts não supervisionados
Versões de Stack: Esta página foi escrita para Python 3.14.0 (3.14 estável, 3.13 em manutenção), FastAPI 0.115+, Django 5.2, Flask 3.1, Pydantic 2, PyTorch 2.6+, pandas 2.2+, Polars 1.x, ruff 0.9+ e uv 0.6+.