Bem-vindo à aula sobre pathlib, a biblioteca moderna do Python para manipulação de caminhos de arquivos. Se você já usou os.path, sabe que trabalhar com caminhos pode ser trabalhoso e propenso a erros, especialmente ao lidar com diferentes sistemas operacionais. O pathlib, introduzido na versão 3.4 e aprimorado nas seguintes, oferece uma abordagem orientada a objetos que torna o código mais legível, expressivo e portátil.

Nesta aula, vamos nos aprofundar na classe Path, explorar as operações mais comuns, comparar com os.path e discutir boas práticas para que você possa adotar pathlib em seus projetos com confiança. Ao final, você terá exercícios para consolidar o conhecimento.

Path

A classe Path é o coração do pathlib. Ela representa um caminho (path) de arquivo ou diretório de forma abstrata, independente do sistema operacional. Em vez de strings simples, você trabalha com objetos que possuem métodos e propriedades úteis. Para começar, importamos a classe:

from pathlib import Path

Você pode criar um objeto Path de várias maneiras. A mais comum é passar uma string ou usar o construtor com múltiplos argumentos:

# A partir de uma string
caminho = Path('/home/usuario/documentos/arquivo.txt')

# Com partes separadas
caminho = Path('home', 'usuario', 'documentos', 'arquivo.txt')

# Caminho relativo
relativo = Path('documentos/arquivo.txt')

Uma das grandes vantagens é que os objetos Path se adaptam ao sistema operacional em uso. No Windows, por exemplo, a barra invertida é usada, mas você pode usar a barra normal (/) e o Python converte automaticamente. Isso torna seu código portátil sem esforço.

Outra característica importante é que Path é uma classe que pode ser subclassificada, mas na prática usamos as subclasses PosixPath (Linux/Mac) e WindowsPath (Windows) automaticamente. Você também pode obter o caminho atual com Path.cwd() e o caminho do home do usuário com Path.home().

Operações

O pathlib oferece uma vasta gama de operações para lidar com caminhos. Vamos explorar as mais essenciais.

Verificações e propriedades

Você pode facilmente verificar se um caminho existe, se é um arquivo ou diretório, e obter informações como nome e sufixo:

p = Path('documentos/arquivo.txt')
print(p.exists())      # True se existir
print(p.is_file())     # True se for arquivo
print(p.is_dir())      # True se for diretório
print(p.name)          # 'arquivo.txt'
print(p.stem)          # 'arquivo' (sem extensão)
print(p.suffix)        # '.txt'
print(p.parent)        # 'documentos'

Manipulação de caminhos

Para construir novos caminhos, você pode usar o operador / (divisão) para concatenar partes. Isso é muito mais limpo que usar os.path.join:

base = Path('/home/usuario')
arquivo = base / 'documentos' / 'arquivo.txt'
print(arquivo)  # /home/usuario/documentos/arquivo.txt

Para obter o caminho absoluto, use resolve(), que também normaliza o caminho:

relativo = Path('documentos/arquivo.txt')
absoluto = relativo.resolve()
print(absoluto)  # /home/usuario/documentos/arquivo.txt (caminho completo)

Operações de arquivo e diretório

O pathlib inclui métodos para criar, renomear, excluir e listar arquivos/diretórios. Veja alguns exemplos:

# Criar diretório (incluindo pais, se necessário)
p = Path('novo/diretorio')
p.mkdir(parents=True, exist_ok=True)

# Criar arquivo vazio
arquivo = p / 'exemplo.txt'
arquivo.touch()

# Renomear
arquivo.rename(p / 'renomeado.txt')

# Excluir arquivo
(p / 'renomeado.txt').unlink()

# Listar conteúdo de um diretório
for item in p.iterdir():
    print(item)

Para ler e escrever arquivos, pathlib oferece métodos convenientes como read_text() e write_text(), que gerenciam a abertura e fechamento do arquivo:

arquivo = Path('exemplo.txt')
arquivo.write_text('Olá, mundo!')
conteudo = arquivo.read_text()
print(conteudo)  # Olá, mundo!

Trabalhando com glob

O método glob() permite buscar arquivos que correspondam a um padrão, semelhante ao glob do módulo glob:

for txt in Path('.').glob('*.txt'):
    print(txt)

Isso é extremamente útil para processar múltiplos arquivos de uma vez.

vs os.path

Antes do pathlib, a forma tradicional era usar o módulo os.path, que trabalha com strings. Vamos comparar as duas abordagens para entender as vantagens do pathlib.

Exemplo com os.path:

import os

caminho = os.path.join('/home/usuario', 'documentos', 'arquivo.txt')
print(os.path.basename(caminho))  # arquivo.txt
print(os.path.dirname(caminho))   # /home/usuario/documentos
print(os.path.splitext(caminho)[0])  # /home/usuario/documentos/arquivo

Exemplo equivalente com pathlib:

from pathlib import Path

caminho = Path('/home/usuario') / 'documentos' / 'arquivo.txt'
print(caminho.name)       # arquivo.txt
print(caminho.parent)     # /home/usuario/documentos
print(caminho.stem)       # arquivo

Diferenças principais:

  • Orientado a objetos: pathlib usa objetos com métodos, enquanto os.path usa funções que recebem strings.
  • Portabilidade: pathlib lida automaticamente com separadores do sistema, os.path também, mas de forma menos integrada.
  • Legibilidade: O operador / para concatenar é mais intuitivo que os.path.join.
  • Riqueza de métodos: Path tem métodos como read_text(), glob(), mkdir() que em os.path exigem chamadas adicionais a os ou glob.

Embora os.path ainda seja amplamente usado e funcione bem, o pathlib é recomendado para código novo, pois é mais limpo e menos propenso a erros.

Boas práticas

Ao usar pathlib, siga estas recomendações para escrever código robusto e claro:

  • Prefira Path a strings: Sempre que você for manipular caminhos, use Path em vez de strings, a menos que esteja lidando com APIs que exijam strings (como open(), que aceita ambos, mas prefira Path).
  • Use / para concatenar: Em vez de os.path.join, use o operador / para construir caminhos, deixando o código mais legível.
  • Aproveite os métodos convenientes: Use read_text(), write_text(), glob(), mkdir(), etc., para reduzir o boilerplate.
  • Trate exceções: Operações como mkdir() podem lançar FileExistsError se o diretório já existir (a menos que exist_ok=True). Sempre trate ou use os parâmetros adequados.
  • Seja consistente: Escolha entre caminhos absolutos e relativos com base no contexto, mas documente suas escolhas.
  • Evite os.chdir(): Em vez de mudar o diretório de trabalho, use caminhos absolutos ou relativos a partir de um ponto fixo.

Um exemplo de boas práticas em um script:

from pathlib import Path

def processar_arquivos(diretorio: str):
    base = Path(diretorio)
    if not base.is_dir():
        raise ValueError(f'{diretorio} não é um diretório válido')
    
    for arquivo in base.glob('*.txt'):
        conteudo = arquivo.read_text(encoding='utf-8')
        # Processa o conteúdo...
        print(f'Processado: {arquivo.name}')

Referências

Exercícios

  1. Crie um objeto Path que represente o arquivo dados.csv dentro do diretório projeto no diretório home do usuário. Imprima o caminho absoluto.

    ✓ Resposta:
    from pathlib import Path
    
    caminho = Path.home() / 'projeto' / 'dados.csv'
    print(caminho.resolve())
    
  2. Escreva um código que crie o diretório backup dentro do diretório atual, se ele não existir, e depois crie um arquivo vazio chamado backup.txt dentro dele.

    ✓ Resposta:
    from pathlib import Path
    
    backup_dir = Path('backup')
    backup_dir.mkdir(exist_ok=True)
    (backup_dir / 'backup.txt').touch()
    
  3. Dada uma lista de arquivos com extensão .log em um diretório, escreva um script que imprima o nome de cada arquivo (sem extensão) e o tamanho em bytes.

    ✓ Resposta:
    from pathlib import Path
    
    for arquivo in Path('.').glob('*.log'):
        tamanho = arquivo.stat().st_size
        print(f'{arquivo.stem}: {tamanho} bytes')
    
  4. Explique a diferença entre Path.resolve() e Path.absolute(). Em que situação você usaria um em vez do outro?

    ✓ Resposta:

    Path.resolve() retorna o caminho absoluto, resolvendo symlinks e normalizando o caminho (removendo ..). Path.absolute() apenas prefixa o diretório de trabalho atual, sem resolver symlinks ou normalizar. Use resolve() quando precisar de um caminho canônico e absolute() quando apenas precisar do caminho absoluto sem se preocupar com symlinks.

  5. Escreva uma função que receba um caminho de um arquivo e retorne o conteúdo do arquivo em letras maiúsculas, usando pathlib. Se o arquivo não existir, retorne uma string vazia.

    ✓ Resposta:
    from pathlib import Path
    
    def ler_em_maiusculas(caminho: str) -> str:
        p = Path(caminho)
        if p.exists() and p.is_file():
            return p.read_text(encoding='utf-8').upper()
        return ''
    

Observações finais: O pathlib é uma ferramenta poderosa que simplifica a manipulação de caminhos. Ao incorporá-lo no seu dia a dia, você escreverá código mais limpo e portátil. Lembre-se de explorar a documentação oficial e praticar com projetos reais para fixar o conteúdo.