Tutorial

Como usar o módulo pathlib para manipular os caminhos de sistema de arquivos no Python 3

PythonDevelopment

O autor selecionou a COVID-19 Relief Fund​​​​​ para receber uma doação como parte do programa Write for DOnations.

Introdução

O Python 3 inclui o módulo pathlib para manipular caminhos de sistema de arquivos de maneira independente, seja qual for o sistema operacional. O pathlib é semelhante ao módulo os.path, mas o pathlib oferece um nível mais elevado — e muitas vezes mais conveniente — de interface do que o os.path.

Podemos identificar arquivos em um computador com caminhos hierárquicos. Por exemplo, podemos identificar o arquivo wave.txt em um computador com este caminho: /Users/sammy/ocean/wave.txt. Os sistemas operacionais representam caminhos de maneira ligeiramente diferente. O Windows pode representar o caminho para o arquivo wave.txt como C:\Users\sammy\ocean\wave.txt.

O módulo pathlib pode ser útil para você se em seu programa Python você estiver criando ou movendo arquivos no sistema de arquivos, listando arquivos no sistema de arquivos em que todos correspondam a uma dada extensão ou padrão, ou criando caminhos de arquivo apropriados ao sistema operacional baseados em coleções de strings brutas. Embora seja possível usar outras ferramentas (como o módulo os.path) para realizar muitas dessas tarefas, o módulo pathlib permite que você execute essas operações com um alto grau de legibilidade e uma quantidade mínima de código.

Neste tutorial, vamos revisar algumas das maneiras de usar o módulo pathlib para representar e manipular os caminhos de sistema de arquivos.

Pré-requisitos

Para tirar o máximo proveito deste tutorial, é recomendado ter alguma familiaridade com programação em Python 3. Você pode revisar esses tutoriais para as informações básicas necessárias:

Construindo instâncias Path

O módulo pathlib oferece várias classes, mas uma das mais importantes é a classe Path. As instâncias da classe Path representam um caminho para um arquivo ou diretório no sistema de arquivos do nosso computador.

Por exemplo, o código a seguir cria uma instância Path que representa parte do caminho para um arquivo wave.txt:

from pathlib import Path

wave = Path("ocean", "wave.txt")
print(wave)

Se executarmos esse código, receberemos um resultado como o seguinte:

Output
ocean/wave.txt

from pathlib import Path torna a classe Path disponível para nosso programa. Em seguida, Path("ocean", "wave.txt") cria uma nova instância do Path. Imprimir o resultado mostra que o Python adicionou o separador de sistema operacional / apropriado entre os dois componentes do caminho que demos a ele: "ocean" e "wave.txt".

Nota: dependendo do seu sistema operacional, o resultado pode variar ligeiramente dos resultados de exemplo exibidos neste tutorial. Se estiver utilizando o Windows, por exemplo, seu resultado para este primeiro exemplo se pareceria com ocean\wave.txt.

Agora, o objeto Path atribuído à variável wave contém um caminho relativo. Em outras palavras, ocean/wave.txt pode existir em vários lugares em nosso sistema de arquivos. Para exemplificar, ele pode existir em /Users/user_1/ocean/wave.txt ou /Users/user_2/research/ocean/wave.txt, mas não especificamos exatamente a qual deles estamos nos referindo. Um caminho absoluto, por outro lado, refere-se sem sombra de dúvidas a uma localização específica no sistema de arquivos.

Use o Path.home() para obter o caminho absoluto para o diretório home do usuário atual:

home = Path.home()
wave_absolute = Path(home, "ocean", "wave.txt")
print(home)
print(wave_absolute)

Se executarmos esse código, receberemos um resultado parecido com o seguinte:

Output
/Users/sammy /Users/sammy/ocean/wave.txt

Nota: como mencionado anteriormente, seu resultado irá variar dependendo do seu sistema operacional. Seu diretório home, por consequência, também será diferente de /Users/sammy.

Path.home() retorna uma instância Path com um caminho absoluto para o diretório home do usuário atual. Em seguida, passamos essa instância Path e as strings "ocean" e "wave.txt" para outro construtor Path de forma a criar um caminho absoluto para o arquivo wave.txt. O resultado mostra que a primeira linha é o diretório home, e a segunda linha é o diretório home mais ocean/wave.txt.

Este exemplo também ilustra uma característica importante da classe Path: o construtor Path aceita tanto strings quanto objetos Path pré-existentes.

Vamos analisar as strings e objetos Path no construtor Path um pouco mais de perto:

shark = Path(Path.home(), "ocean", "animals", Path("fish", "shark.txt"))
print(shark)

Se executarmos esse código Python, receberemos um resultado semelhante ao seguinte:

Output
/Users/sammy/ocean/animals/fish/shark.txt

shark é um Path para um arquivo que construímos usando dois objetos Path (Path.home() e Path("fish", "shark.txt")) e as strings ("ocean" e "animals"). O construtor Path lida com os dois tipos de objetos de maneira inteligente e une-os corretamente usando o separador de sistema operacional adequado, neste caso, /.

Acessando os atributos de arquivo

Agora que aprendemos como construir instâncias Path, vamos analisar como você pode usar essas instâncias para acessar informações sobre um arquivo.

Podemos usar os atributos name e suffix para acessar os nomes e sufixos dos arquivos:

wave = Path("ocean", "wave.txt")
print(wave)
print(wave.name)
print(wave.suffix)

Ao executar este código, receberemos um resultado semelhante ao seguinte:

Output
/Users/sammy/ocean/wave.txt wave.txt .txt

Este resultado mostra que o nome do arquivo no final do nosso caminho é wave.txt e o sufixo desse arquivo é .txt.

As instâncias Path também oferecem a função with_name que permite criar rapidamente um novo objeto Path com um nome diferente:

wave = Path("ocean", "wave.txt")
tides = wave.with_name("tides.txt")
print(wave)
print(tides)

Se executarmos o código acima, receberemos um resultado como o seguinte:

ocean/wave.txt
ocean/tides.txt

Primeiro, o código constrói uma instância Path que aponta para um arquivo chamado wave.txt. Em seguida, chamamos o método with_name em wave para retornar uma segunda instância Path que aponta para um novo arquivo chamado tides.txt. A porção de diretório ocean/ do caminho permanece inalterada, deixando o caminho final como sendo ocean/tides.txt

Acessando diretórios ancestrais

Às vezes, é útil acessar diretórios que contêm um dado caminho. Vamos considerar um exemplo:

shark = Path("ocean", "animals", "fish", "shark.txt")
print(shark)
print(shark.parent)

Se executarmos esse código, receberemos um resultado parecido com o seguinte:

Output
ocean/animals/fish/shark.txt ocean/animals/fish

O atributo parent em uma instância Path retorna o ancestral mais próximo de um determinado caminho de arquivo. Neste caso, ele retorna o diretório que contém o arquivo shark.txt: ocean/animals/fish.

Podemos acessar o atributo parent várias vezes seguidas para percorrer a árvore de ancestralidade de um dado arquivo:

shark = Path("ocean", "animals", "fish", "shark.txt")
print(shark)
print(shark.parent.parent)

Se executarmos esse código, receberemos o seguinte resultado:

Output
ocean/animals/fish/shark.txt ocean/animals

O resultado é semelhante ao resultado anterior, mas agora percorremos mais um nível acessando .parent uma segunda vez. Dois diretórios acima de shark.txt, você encontrará o diretório ocean/animals.

Usando Glob para listar arquivos

Também é possível usar a classe Path para listar arquivos usando o método glob.

Suponha que tivéssemos uma estrutura de diretório que se parecia com esta:

└── ocean
    ├── animals
    │   └── fish
    │       └── shark.txt
    ├── tides.txt
    └── wave.txt

Um diretório ocean contém os arquivos tides.txt e wave.txt. Temos um arquivo chamado shark.txt contido no diretório ocean, um diretório animals e um diretório fish: ocean/animals/fish.

Para listar todos os arquivos .txt no diretório ocean, podemos utilizar:

for txt_path in Path("ocean").glob("*.txt"):
    print(txt_path)

Esse código produziria um resultado como este:

Output
ocean/wave.txt ocean/tides.txt

O padrão glob "*.txt" encontra todos os arquivos terminados em .txt. Como a amostra de código executa esse glob no diretório ocean, ela retorna os dois arquivos .txt no diretório ocean: wave.txt e tides.txt.

Nota: se você quiser replicar os resultados mostrados neste exemplo, você precisará imitar a estrutura de diretórios aqui ilustrada em seu computador.

Também podemos usar o método glob recursivamente. Para listar todos os arquivos .txt no diretório ocean e todos os seus subdiretórios, podemos utilizar:

for txt_path in Path("ocean").glob("**/*.txt"):
    print(txt_path)

Se executarmos esse código, receberemos um resultado como o seguinte:

Output
ocean/wave.txt ocean/tides.txt ocean/animals/fish/shark.txt

A parte ** do padrão glob irá corresponder a esse diretório e todos os diretórios abaixo dele, recursivamente. Dessa forma, não só temos os arquivos wave.txt e tides.txt no resultado, mas também recebemos o arquivo shark.txt que estava contido em ocean/animals/fish.

Computando caminhos relativos

Podemos usar o método Path.relative_to para computar caminhos em relação uns aos outros. O método relative_to é útil quando, por exemplo, você quiser recuperar parte de um caminho de arquivo longo.

Considere o código a seguir:

shark = Path("ocean", "animals", "fish", "shark.txt")
below_ocean = shark.relative_to(Path("ocean"))
below_animals = shark.relative_to(Path("ocean", "animals"))
print(shark)
print(below_ocean)
print(below_animals)

Se executarmos o código acima, receberemos um resultado como o seguinte:

Output
ocean/animals/fish/shark.txt animals/fish/shark.txt fish/shark.txt

O método relative_to retorna um novo objeto Path relativo ao argumento dado. Em nosso exemplo, computamos o Path para o shark.txt relativo ao diretório ocean, e então relativo tanto ao diretório ocean quanto ao diretório animals.

Se relative_to não puder computar uma resposta porque lhe fornecemos um caminho não relacionado, ele gera um ValueError:

shark = Path("ocean", "animals", "fish", "shark.txt")
shark.relative_to(Path("unrelated", "path"))

Receberemos uma exceção ValueError gerada a partir deste código que será algo parecido com isto:

Output
Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/Python3.8/pathlib.py", line 899, in relative_to raise ValueError("{!r} does not start with {!r}" ValueError: 'ocean/animals/fish/shark.txt' does not start with 'unrelated/path'

unrelated/path não faz parte de ocean/animals/fish/shark.txt, então não existe nenhuma maneira para o Python computar um caminho relativo.

Conclusão

O módulo pathlib é uma parte poderosa da Biblioteca Padrão do Python que nos permite manipular caminhos do sistema de arquivos rapidamente em qualquer sistema operacional. Neste tutorial, aprendemos a usar alguns utilitários chave do pathlib para acessar atributos de arquivo, listar arquivos com padrões glob e percorrer arquivos e diretórios pais.

O módulo pathlib também oferece classes e utilitários adicionais que não abordamos neste tutorial. Agora que você tem um conhecimento base, use a documentação do módulo pathlib para aprender mais sobre outras classes e utilitários disponíveis.

Se estiver interessado em usar outras bibliotecas do Python, confira os seguintes tutoriais:

Creative Commons License