Skip to content

Python Pathlib: O Guia Moderno para Manipulação de Caminhos de Arquivos

Updated on

Se você já escreveu código Python como os.path.join(os.path.dirname(os.path.abspath(__file__)), 'data', 'output'), você já conhece o problema. A manipulação de caminhos de arquivos baseada em strings com os.path é verbosa, difícil de ler e propensa a erros. Você concatena strings e esquece os separadores. Você codifica / e o script quebra no Windows. Você encadeia cinco chamadas os.path apenas para obter o nome stem de um arquivo, e três meses depois ninguém consegue ler o código -- incluindo você.

Estes não são casos extremos. Todo pipeline de ciência de dados, toda aplicação web e todo script de automação toca no sistema de arquivos. Caminhos que funcionam no seu Mac falham no laptop Windows de um colega. Variáveis de caminho temporário se acumulam no seu código como dívida técnica. E quanto mais chamadas os.path.join(os.path.dirname(...)) você aninha, mais provável é introduzir um bug sutil que só aparece em produção.

O módulo pathlib do Python resolve isso. Introduzido no Python 3.4 e totalmente maduro desde o Python 3.6, pathlib substitui a manipulação de caminhos baseada em strings por objetos Path próprios. Caminhos são unidos com o operador /. Atributos de arquivo como .name, .suffix e .stem são propriedades, não chamadas de função. Ler e escrever arquivos leva uma linha. E tudo funciona identicamente entre sistemas operacionais. Este guia cobre todos os recursos essenciais do pathlib, desde a construção básica de caminhos até padrões avançados para fluxos de trabalho de ciência de dados.

📚

Por que pathlib em vez de os.path

Antes do pathlib, desenvolvedores Python dependiam de os.path para operações de caminho e os para interações com o sistema de arquivos. Essa abordagem funciona, mas trata caminhos como strings simples. Isso cria três problemas persistentes:

  1. A legibilidade se degrada rapidamente. Compare os.path.splitext(os.path.basename(filepath))[0] com Path(filepath).stem. Ambos extraem o nome do arquivo sem sua extensão. Um é autoexplicativo; o outro requer análise mental.

  2. Bugs multiplataforma. Codificar / como separador ou usar concatenação de strings significa que seu script Linux quebra silenciosamente no Windows. os.path.join ajuda, mas esquecer de usá-lo mesmo uma vez cria um bug latente.

  3. Funcionalidade dispersa. Para trabalhar com caminhos você precisa de os.path para decomposição, os para criação de diretórios, glob para correspondência de padrões e open() para E/S de arquivos. pathlib consolida tudo isso em um único objeto Path.

Aqui está a mesma tarefa -- encontrar todos os arquivos .csv em um diretório de dados e ler o primeiro -- em ambos os estilos:

# abordagem com os.path
import os
import glob
 
data_dir = os.path.join(os.path.expanduser('~'), 'projects', 'data')
csv_files = glob.glob(os.path.join(data_dir, '**', '*.csv'), recursive=True)
if csv_files:
    with open(csv_files[0], 'r') as f:
        content = f.read()
# abordagem com pathlib
from pathlib import Path
 
data_dir = Path.home() / 'projects' / 'data'
csv_files = list(data_dir.rglob('*.csv'))
if csv_files:
    content = csv_files[0].read_text()

A versão pathlib é mais curta, mais fácil de ler e faz exatamente a mesma coisa. Nenhuma importação além de Path. Nenhuma concatenação de strings. Nenhuma chamada open() separada.

Criando Objetos Path

Toda operação pathlib começa com a criação de um objeto Path. A classe Path retorna automaticamente um PosixPath no Linux/macOS ou um WindowsPath no Windows.

from pathlib import Path
 
# A partir de uma string
p = Path('/home/user/documents/report.csv')
 
# A partir de múltiplos segmentos (unidos automaticamente)
p = Path('home', 'user', 'documents', 'report.csv')
 
# Diretório de trabalho atual
cwd = Path.cwd()
print(cwd)  # ex., /home/user/projects/myapp
 
# Diretório home do usuário
home = Path.home()
print(home)  # ex., /home/user
 
# Caminho relativo
p = Path('data/output/results.csv')
 
# A partir de um caminho existente
base = Path('/home/user')
full = Path(base, 'documents', 'file.txt')
print(full)  # /home/user/documents/file.txt

Path() sem argumentos retorna Path('.'), um caminho relativo para o diretório atual. Use Path.cwd() quando precisar do diretório atual absoluto.

Unindo Caminhos com o Operador /

O recurso mais distintivo do pathlib é seu operador / sobrecarregado. Em vez de os.path.join(), você encadeia segmentos de caminho com /:

from pathlib import Path
 
# Construa caminhos naturalmente
project = Path.home() / 'projects' / 'analysis'
data_file = project / 'data' / 'sales_2026.csv'
print(data_file)  # /home/user/projects/analysis/data/sales_2026.csv
 
# Misture objetos Path e strings
base = Path('/var/log')
app_log = base / 'myapp' / 'error.log'
print(app_log)  # /var/log/myapp/error.log
 
# Combine com variáveis
filename = 'report.pdf'
output = Path('output') / filename
print(output)  # output/report.pdf

O operador / lida com separadores automaticamente. No Windows, Path('C:/Users') / 'data' produz C:\Users\data. Você nunca mais precisa pensar sobre / vs \.

Você também pode usar joinpath() para o mesmo resultado:

from pathlib import Path
 
# Equivalente a Path('data') / 'raw' / 'file.csv'
p = Path('data').joinpath('raw', 'file.csv')
print(p)  # data/raw/file.csv

Componentes do Caminho

Todo objeto Path expõe seus componentes como propriedades. Nenhuma chamada de função, nenhuma divisão de string.

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/sales_report.final.csv')
 
print(p.name)       # sales_report.final.csv  (nome do arquivo com extensão)
print(p.stem)       # sales_report.final      (nome do arquivo sem última extensão)
print(p.suffix)     # .csv                    (última extensão)
print(p.suffixes)   # ['.final', '.csv']      (todas as extensões)
print(p.parent)     # /home/user/projects/analysis/data
print(p.anchor)     # /                       (raiz no Unix, C:\ no Windows)
print(p.parts)      # ('/', 'home', 'user', 'projects', 'analysis', 'data', 'sales_report.final.csv')

Navegando por Parents

A propriedade .parent retorna o diretório pai imediato. Encadeie para subir mais:

from pathlib import Path
 
p = Path('/home/user/projects/analysis/data/output.csv')
 
print(p.parent)            # /home/user/projects/analysis/data
print(p.parent.parent)     # /home/user/projects/analysis
print(p.parent.parent.parent)  # /home/user/projects
 
# .parents dá acesso indexado a todos os ancestrais
print(p.parents[0])  # /home/user/projects/analysis/data
print(p.parents[1])  # /home/user/projects/analysis
print(p.parents[2])  # /home/user/projects
print(p.parents[3])  # /home/user

Alterando Componentes do Caminho

Use .with_name(), .with_stem() e .with_suffix() para criar novos caminhos com componentes modificados:

from pathlib import Path
 
p = Path('/data/reports/sales_q1.csv')
 
# Altera o nome do arquivo inteiramente
print(p.with_name('revenue_q1.csv'))    # /data/reports/revenue_q1.csv
 
# Altera apenas o stem (Python 3.9+)
print(p.with_stem('sales_q2'))          # /data/reports/sales_q2.csv
 
# Altera apenas a extensão
print(p.with_suffix('.parquet'))        # /data/reports/sales_q1.parquet
 
# Remove a extensão
print(p.with_suffix(''))                # /data/reports/sales_q1
 
# Adiciona uma extensão
backup = p.with_suffix(p.suffix + '.bak')
print(backup)                           # /data/reports/sales_q1.csv.bak

Estes métodos retornam novos objetos Path. Eles não renomeiam arquivos no disco.

E/S de Arquivos: Lendo e Escrevendo

pathlib elimina o boilerplate open() / with para operações simples de arquivo:

from pathlib import Path
 
file_path = Path('example.txt')
 
# Escreve texto em um arquivo (cria se não existir, sobrescreve se existir)
file_path.write_text('Hello, pathlib!\nSecond line.')
 
# Lê texto de um arquivo
content = file_path.read_text()
print(content)
# Hello, pathlib!
# Second line.
 
# Escreve bytes
binary_path = Path('data.bin')
binary_path.write_bytes(b'\x00\x01\x02\x03')
 
# Lê bytes
raw = binary_path.read_bytes()
print(raw)  # b'\x00\x01\x02\x03'

Especifique a codificação explicitamente ao trabalhar com texto não-ASCII:

from pathlib import Path
 
# Escreve texto UTF-8
Path('greeting.txt').write_text('こんにちは世界', encoding='utf-8')
 
# Lê com codificação
text = Path('greeting.txt').read_text(encoding='utf-8')
print(text)  # こんにちは世界

Para arquivos grandes ou operações de streaming, use .open() que retorna um handle de arquivo igual ao open() embutido:

from pathlib import Path
 
log_file = Path('application.log')
 
# Escreve linha por linha
with log_file.open('w') as f:
    for i in range(1000):
        f.write(f'Event {i}: processed\n')
 
# Lê linha por linha (eficiente em memória para arquivos grandes)
with log_file.open('r') as f:
    for line in f:
        if 'error' in line.lower():
            print(line.strip())

Operações de Diretório

Criando Diretórios

from pathlib import Path
 
# Cria um único diretório
Path('output').mkdir()
 
# Cria com parents (como os.makedirs)
Path('data/raw/2026/february').mkdir(parents=True, exist_ok=True)
 
# parents=True cria todos os diretórios pai ausentes
# exist_ok=True evita erro se o diretório já existir

Um erro comum é esquecer parents=True. Sem ele, mkdir() levanta FileNotFoundError se algum diretório pai estiver ausente. Sempre use parents=True ao criar diretórios aninhados, e exist_ok=True para tornar a operação idempotente.

Listando Conteúdo de Diretórios

from pathlib import Path
 
project = Path('.')
 
# Lista todas as entradas (arquivos e diretórios)
for entry in project.iterdir():
    print(entry.name, '(dir)' if entry.is_dir() else '(file)')
 
# Filtra apenas arquivos
files = [f for f in project.iterdir() if f.is_file()]
print(f"Found {len(files)} files")
 
# Filtra apenas diretórios
dirs = [d for d in project.iterdir() if d.is_dir()]
print(f"Found {len(dirs)} directories")
 
# Ordena por nome
for entry in sorted(project.iterdir()):
    print(entry.name)

Removendo Diretórios e Arquivos

from pathlib import Path
 
# Remove um arquivo
Path('temp_output.csv').unlink()
 
# Remove um arquivo apenas se existir (Python 3.8+)
Path('temp_output.csv').unlink(missing_ok=True)
 
# Remove um diretório vazio
Path('empty_dir').rmdir()

rmdir() remove apenas diretórios vazios. Para diretórios não vazios, use shutil.rmtree():

from pathlib import Path
import shutil
 
target = Path('data/old_output')
if target.exists():
    shutil.rmtree(target)

Padrões Glob: Encontrando Arquivos

pathlib tem suporte glob embutido. Não é necessário importar o módulo glob separadamente.

Glob Básico

from pathlib import Path
 
project = Path('/home/user/project')
 
# Encontra todos os arquivos Python em um diretório
for py_file in project.glob('*.py'):
    print(py_file.name)
 
# Encontra todos os arquivos CSV
csv_files = list(project.glob('*.csv'))
print(f"Found {len(csv_files)} CSV files")
 
# Encontra arquivos correspondendo a um padrão
reports = list(project.glob('report_*.xlsx'))

Glob Recursivo com rglob

rglob() busca recursivamente através de todos os subdiretórios. É equivalente a glob('**/*.pattern') mas mais conveniente:

from pathlib import Path
 
project = Path('/home/user/project')
 
# Encontra todos os arquivos Python em todos os subdiretórios
all_py = list(project.rglob('*.py'))
print(f"Found {len(all_py)} Python files across all directories")
 
# Encontra todos os Jupyter notebooks recursivamente
notebooks = list(project.rglob('*.ipynb'))
for nb in notebooks:
    print(f"  {nb.relative_to(project)}")
 
# Encontra todos os arquivos de imagem
images = list(project.rglob('*.png')) + list(project.rglob('*.jpg'))
 
# Encontra todos os arquivos (sem filtro)
all_files = [f for f in project.rglob('*') if f.is_file()]

Padrões Glob Avançados

from pathlib import Path
 
data = Path('data')
 
# Curinga de caractere único
data.glob('file_?.csv')        # file_1.csv, file_a.csv
 
# Intervalos de caracteres
data.glob('report_202[456].csv')  # report_2024.csv, report_2025.csv, report_2026.csv
 
# Qualquer nível de subdiretório
data.glob('**/output/*.csv')   # data/raw/output/result.csv, data/processed/output/result.csv
 
# Múltiplas extensões (combina dois globs)
from itertools import chain
all_data = chain(data.rglob('*.csv'), data.rglob('*.parquet'))

Verificando Caminhos

pathlib fornece métodos booleanos claros para verificar status de caminho:

from pathlib import Path
 
p = Path('/home/user/projects/data.csv')
 
# O caminho existe?
print(p.exists())       # True ou False
 
# É um arquivo?
print(p.is_file())      # True se existe e é um arquivo regular
 
# É um diretório?
print(p.is_dir())       # True se existe e é um diretório
 
# É um link simbólico?
print(p.is_symlink())   # True se existe e é um symlink
 
# É um caminho absoluto?
print(p.is_absolute())  # True (/home/... começa com raiz)
print(Path('data.csv').is_absolute())  # False (caminho relativo)

Estes métodos nunca levantam exceções para caminhos inexistentes. Eles simplesmente retornam False, o que os torna seguros para usar em condicionais:

from pathlib import Path
 
config = Path('config.yaml')
if config.is_file():
    settings = config.read_text()
else:
    print("Config file not found, using defaults")

Manipulação de Caminhos

Resolvendo e Normalizando Caminhos

from pathlib import Path
 
# Resolve para caminho absoluto (também resolve symlinks)
p = Path('data/../data/./output.csv')
print(p.resolve())  # /home/user/project/data/output.csv
 
# Obtém caminho absoluto sem resolver symlinks
print(p.absolute())  # /home/user/project/data/../data/./output.csv
 
# Expande diretório home do usuário
p = Path('~/Documents/report.csv')
print(p.expanduser())  # /home/user/Documents/report.csv

Caminhos Relativos

from pathlib import Path
 
full_path = Path('/home/user/projects/analysis/data/output.csv')
base = Path('/home/user/projects')
 
# Obtém o caminho relativo de base para full_path
relative = full_path.relative_to(base)
print(relative)  # analysis/data/output.csv
 
# Isso levanta ValueError se o caminho não é relativo à base
try:
    Path('/var/log/app.log').relative_to(base)
except ValueError as e:
    print(e)  # '/var/log/app.log' is not relative to '/home/user/projects'
 
# Python 3.12+: verificação is_relative_to()
print(full_path.is_relative_to(base))   # True
print(Path('/var/log').is_relative_to(base))  # False

Metadados de Arquivos e Stat

from pathlib import Path
from datetime import datetime
 
p = Path('data.csv')
 
# Obtém estatísticas do arquivo
stat = p.stat()
print(f"Size: {stat.st_size} bytes")
print(f"Modified: {datetime.fromtimestamp(stat.st_mtime)}")
print(f"Created: {datetime.fromtimestamp(stat.st_ctime)}")
 
# Conveniência: obtém tamanho diretamente (através de stat)
size_mb = p.stat().st_size / (1024 * 1024)
print(f"Size: {size_mb:.2f} MB")
 
# Verifica se dois caminhos apontam para o mesmo arquivo
p1 = Path('/home/user/data.csv')
p2 = Path.home() / 'data.csv'
print(p1.samefile(p2))  # True (se resolvem para o mesmo arquivo)

Renomeando e Movendo Arquivos

from pathlib import Path
 
# Renomeia um arquivo (retorna o novo Path)
old = Path('report_draft.csv')
new = old.rename('report_final.csv')
print(new)  # report_final.csv
 
# Move para um diretório diferente
source = Path('output/temp_results.csv')
dest = source.rename(Path('archive') / source.name)
 
# Substitui um arquivo (sobrescreve se o destino existir)
Path('new_data.csv').replace('data.csv')

Nota: .rename() sobrescreverá o arquivo de destino no Unix mas pode levantar um erro no Windows. Use .replace() para comportamento de sobrescrita garantido multiplataforma.

os.path vs pathlib: Comparação Completa

Aqui está uma tabela de referência mapeando cada operação comum os.path para seu equivalente pathlib:

Operaçãoos.path / ospathlib
Juntar caminhosos.path.join('a', 'b')Path('a') / 'b'
Diretório atualos.getcwd()Path.cwd()
Diretório homeos.path.expanduser('~')Path.home()
Caminho absolutoos.path.abspath(p)Path(p).resolve()
Nome do arquivoos.path.basename(p)Path(p).name
Diretórioos.path.dirname(p)Path(p).parent
Extensãoos.path.splitext(p)[1]Path(p).suffix
Stem (nome sem ext)os.path.splitext(os.path.basename(p))[0]Path(p).stem
Existeos.path.exists(p)Path(p).exists()
É arquivoos.path.isfile(p)Path(p).is_file()
É diretórioos.path.isdir(p)Path(p).is_dir()
É symlinkos.path.islink(p)Path(p).is_symlink()
É absolutoos.path.isabs(p)Path(p).is_absolute()
Tamanho do arquivoos.path.getsize(p)Path(p).stat().st_size
Listar diretórioos.listdir(p)Path(p).iterdir()
Criar diretórioos.makedirs(p, exist_ok=True)Path(p).mkdir(parents=True, exist_ok=True)
Remover arquivoos.remove(p)Path(p).unlink()
Remover diretórioos.rmdir(p)Path(p).rmdir()
Renomearos.rename(old, new)Path(old).rename(new)
Ler arquivoopen(p).read()Path(p).read_text()
Escrever arquivoopen(p, 'w').write(text)Path(p).write_text(text)
Globglob.glob('*.py')Path('.').glob('*.py')
Glob recursivoglob.glob('**/*.py', recursive=True)Path('.').rglob('*.py')
Expandir usuárioos.path.expanduser(p)Path(p).expanduser()
Caminho relativoos.path.relpath(p, base)Path(p).relative_to(base)

Trabalhando com Arquivos Temporários

pathlib se integra limpamente com o módulo tempfile do Python:

from pathlib import Path
import tempfile
 
# Cria um diretório temporário como um Path
with tempfile.TemporaryDirectory() as tmp_dir:
    tmp_path = Path(tmp_dir)
 
    # Escreve arquivos temporários usando pathlib
    data_file = tmp_path / 'intermediate_results.csv'
    data_file.write_text('col1,col2\n1,2\n3,4\n')
 
    config_file = tmp_path / 'run_config.json'
    config_file.write_text('{"epochs": 100, "lr": 0.001}')
 
    # Lista o que criamos
    for f in tmp_path.iterdir():
        print(f"{f.name}: {f.stat().st_size} bytes")
 
    # Processa arquivos...
    print(data_file.read_text())
 
# Diretório e todos os arquivos são automaticamente deletados aqui
from pathlib import Path
import tempfile
 
# Cria um arquivo temporário nomeado
tmp = tempfile.NamedTemporaryFile(suffix='.csv', delete=False)
tmp_path = Path(tmp.name)
tmp.close()
 
# Usa pathlib para escrever nele
tmp_path.write_text('id,value\n1,100\n2,200\n')
print(f"Temp file at: {tmp_path}")
 
# Limpa quando terminar
tmp_path.unlink()

Pathlib em Fluxos de Trabalho de Ciência de Dados

Projetos de ciência de dados tipicamente envolvem ler datasets de múltiplos diretórios, criar pastas de saída para resultados e gerenciar artefatos de experimentos. pathlib torna estes padrões limpos e confiáveis.

Organizando Diretórios de Projeto

from pathlib import Path
 
def setup_experiment(experiment_name):
    """Cria uma estrutura de diretório de experimento padrão."""
    base = Path('experiments') / experiment_name
 
    dirs = ['data/raw', 'data/processed', 'models', 'results/figures', 'results/tables', 'logs']
 
    for d in dirs:
        (base / d).mkdir(parents=True, exist_ok=True)
 
    # Cria um arquivo de configuração
    config = base / 'config.json'
    if not config.exists():
        config.write_text('{"learning_rate": 0.001, "epochs": 50}')
 
    print(f"Experiment directory ready: {base.resolve()}")
    return base
 
project = setup_experiment('sales_forecast_v2')

Lendo Múltiplos Arquivos de Dados

from pathlib import Path
import pandas as pd
 
data_dir = Path('data/raw')
 
# Lê todos os arquivos CSV em um único DataFrame
dfs = []
for csv_file in sorted(data_dir.glob('*.csv')):
    print(f"Loading {csv_file.name}...")
    df = pd.read_csv(csv_file)
    df['source_file'] = csv_file.stem  # Adiciona nome do arquivo fonte
    dfs.append(df)
 
combined = pd.concat(dfs, ignore_index=True)
print(f"Loaded {len(combined)} rows from {len(dfs)} files")
 
# Salva no diretório processado
output_path = Path('data/processed') / 'combined_sales.parquet'
output_path.parent.mkdir(parents=True, exist_ok=True)
combined.to_parquet(output_path)

Após carregar seus dados CSV com pathlib, você pode explorá-los visualmente com PyGWalker (opens in a new tab). Ele transforma qualquer DataFrame Pandas em uma interface interativa tipo Tableau para exploração de dados drag-and-drop -- sem código extra necessário.

Salvando Resultados de Experimentos

from pathlib import Path
from datetime import datetime
import json
 
def save_results(metrics, experiment_dir):
    """Salva métricas de experimento com timestamp."""
    results_dir = Path(experiment_dir) / 'results'
    results_dir.mkdir(parents=True, exist_ok=True)
 
    timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
    output_file = results_dir / f'metrics_{timestamp}.json'
 
    output_file.write_text(json.dumps(metrics, indent=2))
    print(f"Results saved to {output_file}")
    return output_file
 
# Uso
metrics = {'accuracy': 0.94, 'f1_score': 0.91, 'loss': 0.187}
save_results(metrics, 'experiments/sales_forecast_v2')

Gerenciando Caminhos de Arquivos em Notebooks

Ao trabalhar em notebooks Jupyter, caminhos frequentemente quebram porque o diretório de trabalho do notebook pode diferir da raiz do projeto. pathlib torna isso fácil de lidar:

from pathlib import Path
 
# Sempre resolve para caminho absoluto da localização do notebook
NOTEBOOK_DIR = Path.cwd()
PROJECT_ROOT = NOTEBOOK_DIR.parent  # se o notebook está em notebooks/
DATA_DIR = PROJECT_ROOT / 'data'
OUTPUT_DIR = PROJECT_ROOT / 'output'
 
# Agora todos os caminhos são absolutos e confiáveis
train_data = DATA_DIR / 'train.csv'
print(f"Loading: {train_data}")
assert train_data.exists(), f"Missing: {train_data}"

Se você trabalha extensivamente no Jupyter e quer um ambiente com IA que ajude a gerenciar arquivos de projeto e caminhos de dados, RunCell (opens in a new tab) adiciona uma camada de agente IA ao seu notebook. Descreva o que você precisa -- "encontre todos os arquivos Parquet no diretório de dados e carregue o mais recente" -- e ele gera o código pathlib e executa para você.

Padrões Comuns e Receitas

Escrita Segura de Arquivos com Substituição Atômica

Previna corrupção de dados escrevendo para um arquivo temporário primeiro, depois substituindo atomicamente o alvo:

from pathlib import Path
import tempfile
 
def safe_write(target_path, content):
    """Escreve conteúdo em arquivo atomicamente para prevenir corrupção."""
    target = Path(target_path)
    target.parent.mkdir(parents=True, exist_ok=True)
 
    # Escreve para arquivo temp no mesmo diretório
    tmp = tempfile.NamedTemporaryFile(
        mode='w', dir=target.parent, suffix='.tmp', delete=False
    )
    tmp_path = Path(tmp.name)
    try:
        tmp.write(content)
        tmp.close()
        tmp_path.replace(target)  # Atômico na maioria dos sistemas de arquivos
    except Exception:
        tmp_path.unlink(missing_ok=True)
        raise
 
safe_write('config/settings.json', '{"debug": true}')

Renomeação em Lote de Arquivos

from pathlib import Path
 
photos_dir = Path('photos')
 
# Renomeia todos os arquivos .jpeg para .jpg
for f in photos_dir.glob('*.jpeg'):
    f.rename(f.with_suffix('.jpg'))
 
# Adiciona prefixo a todos os arquivos
for i, f in enumerate(sorted(photos_dir.glob('*.jpg')), start=1):
    new_name = f.parent / f'photo_{i:04d}{f.suffix}'
    f.rename(new_name)

Encontrar Arquivos Duplicados por Tamanho

from pathlib import Path
from collections import defaultdict
 
def find_potential_duplicates(directory):
    """Encontra arquivos com tamanhos idênticos (potenciais duplicados)."""
    size_map = defaultdict(list)
 
    for f in Path(directory).rglob('*'):
        if f.is_file():
            size_map[f.stat().st_size].append(f)
 
    # Retorna apenas grupos com mais de um arquivo
    return {size: files for size, files in size_map.items() if len(files) > 1}
 
dupes = find_potential_duplicates('data')
for size, files in dupes.items():
    print(f"\n{size} bytes:")
    for f in files:
        print(f"  {f}")

Construir uma Visualização em Árvore de Arquivos

from pathlib import Path
 
def tree(directory, prefix='', max_depth=3, _depth=0):
    """Imprime uma estrutura em árvore de um diretório."""
    if _depth >= max_depth:
        return
 
    path = Path(directory)
    entries = sorted(path.iterdir(), key=lambda e: (e.is_file(), e.name))
 
    for i, entry in enumerate(entries):
        is_last = (i == len(entries) - 1)
        connector = '└── ' if is_last else '├── '
        print(f'{prefix}{connector}{entry.name}')
 
        if entry.is_dir():
            extension = '    ' if is_last else '│   '
            tree(entry, prefix + extension, max_depth, _depth + 1)
 
tree('my_project', max_depth=3)

Output:

├── data
│   ├── processed
│   │   └── combined.csv
│   └── raw
│       ├── sales_2025.csv
│       └── sales_2026.csv
├── notebooks
│   └── analysis.ipynb
├── output
│   └── figures
└── requirements.txt

Erros Comuns e Como Evitá-los

Erro 1: Comparar Strings com Objetos Path

from pathlib import Path
 
p = Path('data/output.csv')
 
# ERRADO: Comparar string com Path
if p == 'data/output.csv':  # Pode funcionar mas é frágil
    print("Match")
 
# CERTO: Comparar Path com Path, ou usar str()
if p == Path('data/output.csv'):
    print("Match")
 
# CERTO: Converter para string se necessário
if str(p) == 'data/output.csv':
    print("Match")

Erro 2: Esquecer parents=True no mkdir

from pathlib import Path
 
# ERRADO: Levanta FileNotFoundError se 'data' não existir
# Path('data/raw/2026').mkdir()
 
# CERTO: Cria todos os parents ausentes
Path('data/raw/2026').mkdir(parents=True, exist_ok=True)

Erro 3: Usar Concatenação de Strings em vez de /

from pathlib import Path
 
base = Path('/home/user')
 
# ERRADO: Concatenação de strings quebra pathlib
# bad = base + '/data/file.csv'  # TypeError
 
# CERTO: Use o operador /
good = base / 'data' / 'file.csv'

Erro 4: Passar Path para Bibliotecas Que Esperam Strings

A maioria das bibliotecas modernas (Pandas, NumPy, PIL, etc.) aceita objetos Path nativamente. Mas se você encontrar uma biblioteca legada que requer strings, converta explicitamente:

from pathlib import Path
 
p = Path('data/output.csv')
 
# A maioria das bibliotecas aceita Path diretamente
import pandas as pd
df = pd.read_csv(p)  # Funciona bem
 
# Para bibliotecas legadas que precisam de strings
import some_legacy_lib
some_legacy_lib.process(str(p))  # Converte com str()
 
# os.fspath() também funciona (Python 3.6+)
import os
some_legacy_lib.process(os.fspath(p))

Erro 5: Usar Caminhos Codificados (Hardcoded)

from pathlib import Path
 
# ERRADO: Caminho absoluto codificado
# data_path = Path('/home/alice/project/data/sales.csv')
 
# CERTO: Construir a partir de componentes relativos ou dinâmicos
data_path = Path.cwd() / 'data' / 'sales.csv'
 
# CERTO: Construir a partir do diretório home
config_path = Path.home() / '.config' / 'myapp' / 'settings.json'
 
# CERTO: Construir a partir de variável de ambiente
import os
data_root = Path(os.getenv('DATA_DIR', 'data'))
data_path = data_root / 'sales.csv'

Perguntas Frequentes

O que é pathlib em Python?

pathlib é um módulo da biblioteca padrão (introduzido no Python 3.4) que fornece classes orientadas a objetos para trabalhar com caminhos do sistema de arquivos. Em vez de tratar caminhos como strings e usar funções como os.path.join(), você cria objetos Path e usa métodos e operadores. Ele lida automaticamente com diferenças de caminho entre plataformas.

Quando devo usar pathlib em vez de os.path?

Use pathlib para todos os projetos novos em Python 3.6+. Ele produz código mais limpo e legível, consolida operações de caminho em um único objeto e lida automaticamente com questões multiplataforma. A única razão para usar os.path é manter código legado que deve suportar Python 2, ou usar as poucas funções os que não têm equivalente pathlib (como os.environ para variáveis de ambiente).

pathlib funciona no Windows?

Sim. pathlib usa automaticamente objetos WindowsPath no Windows e PosixPath no Linux/macOS. O operador / produz caminhos separados por backslash no Windows. Você escreve o mesmo código em todas as plataformas e pathlib lida com as diferenças.

Posso usar objetos Path com Pandas?

Sim. Desde o Python 3.6 e Pandas 0.21+, você pode passar objetos Path diretamente para pd.read_csv(), pd.read_excel(), df.to_csv() e outras funções de I/O. Nenhuma conversão str() necessária.

Qual é a diferença entre Path.resolve() e Path.absolute()?

.resolve() retorna o caminho absoluto e também resolve quaisquer links simbólicos e componentes ../.. .absolute() retorna o caminho absoluto sem resolver symlinks ou normalizar o caminho. Na maioria dos casos, .resolve() é o que você quer.

Como converto entre objetos Path e strings?

Use str(path) para converter um Path para string. Use Path(string) para criar um Path a partir de uma string. Você também pode usar os.fspath(path) para conversão explícita para string. A maioria das bibliotecas Python modernas aceita objetos Path diretamente, então conversão raramente é necessária.

Conclusão

O módulo pathlib do Python é o padrão moderno para manipulação de caminhos de arquivos. O operador / torna a união de caminhos legível. Propriedades como .name, .stem, .suffix e .parent eliminam cadeias verboas de funções os.path. Métodos embutidos para ler, escrever, criar diretórios e globbing consolidam o que antes requeria os, os.path, glob e open() em uma única API consistente.

A migração de os.path para pathlib é direta: substitua os.path.join() por /, substitua os.path.exists() por .exists(), substitua os.makedirs() por .mkdir(parents=True), e substitua glob.glob() por .glob() ou .rglob(). Toda biblioteca Python major -- Pandas, NumPy, PIL, PyTorch -- agora aceita objetos Path nativamente. Não há razão para evitá-lo em novos projetos.

Comece pequeno. Escolha um script que tem código os.path confuso. Substitua as operações de caminho por pathlib. O código ficará mais curto, mais legível e mais portável. Depois faça o mesmo para o próximo script.

📚