Manipulação de Caminhos de Arquivos e Diretórios em Python

PythonBeginner
Pratique Agora

Introdução

Neste laboratório, você aprenderá a manipular caminhos de arquivos e diretórios em Python usando o módulo moderno pathlib. Embora o módulo mais antigo os.path ainda seja comum, o pathlib oferece uma abordagem orientada a objetos que torna a manipulação de caminhos mais intuitiva e legível. Você aprenderá a criar, inspecionar, validar e manipular caminhos, arquivos e diretórios de uma maneira que funciona em diferentes sistemas operacionais como Windows, macOS e Linux.

Ao final deste laboratório, você estará proficiente em:

  • Juntar componentes de caminho corretamente.
  • Criar e gerenciar diretórios.
  • Diferenciar entre caminhos absolutos e relativos.
  • Validar a existência e o tipo de caminhos.
  • Realizar operações comuns de arquivo, como copiar, mover e excluir.
  • Percorrer árvores de diretórios para encontrar arquivos e pastas.

Operações Básicas de Caminho

Nesta etapa, você aprenderá os fundamentos da manipulação de caminhos usando o módulo pathlib. Isso inclui juntar componentes de caminho, obter o diretório de trabalho atual e criar novos diretórios.

O módulo pathlib fornece uma maneira elegante e multiplataforma de lidar com caminhos do sistema de arquivos. No Windows, os caminhos usam barras invertidas (\), enquanto em sistemas do tipo Unix (Linux, macOS), eles usam barras normais (/). Usar pathlib garante que seu código funcione em qualquer plataforma sem ajustes manuais.

Nota: Se você não está familiarizado com o uso do WebIDE (semelhante ao VS Code), aprenda primeiro neste laboratório.

Primeiro, vamos ver como juntar componentes de caminho. O operador / é um atalho intuitivo para joinpath().

  1. Abra o arquivo main.py no explorador de arquivos à esquerda.
  2. Substitua o conteúdo de main.py pelo seguinte código para juntar componentes de caminho e imprimir o resultado.
from pathlib import Path

## Join path components using the / operator
my_path = Path('usr') / 'bin' / 'spam'
print(my_path)
  1. Execute o script no terminal:
python3 main.py

Você verá a seguinte saída, que demonstra como o pathlib forma corretamente um caminho no estilo Unix:

usr/bin/spam

Em seguida, vamos obter o diretório de trabalho atual usando Path.cwd().

  1. Substitua o conteúdo de main.py pelo seguinte código (isso substitui o código anterior):
from pathlib import Path

## Get the current working directory
cwd = Path.cwd()
print(cwd)
  1. Execute o script novamente:
python3 main.py

A saída mostrará o diretório de trabalho atual, que é /home/labex/project neste ambiente:

/home/labex/project

Finalmente, vamos criar um novo diretório. Um FileNotFoundError ocorre se você tentar criar um diretório onde o diretório pai não existe. Para evitar isso, use mkdir(parents=True), que cria todos os diretórios pais necessários.

  1. Substitua o conteúdo de main.py pelo seguinte código para criar uma estrutura de diretórios aninhada:
from pathlib import Path

## Get the current working directory
cwd = Path.cwd()

## Define the path for the new nested directories
new_dirs = cwd / 'delicious' / 'walnut' / 'waffles'

## Create the directories, including any missing parents
new_dirs.mkdir(parents=True, exist_ok=True)

print(f"Successfully created: {new_dirs}")

O argumento exist_ok=True evita um erro se o diretório já existir. Isso significa que você pode executar o script várias vezes sem erros.

  1. Execute o script:
python3 main.py

O script criará os diretórios aninhados e imprimirá uma mensagem de confirmação:

Successfully created: /home/labex/project/delicious/walnut/waffles

Você pode verificar a criação do diretório executando ls -R delicious no terminal.

Caminhos Absolutos vs. Relativos

Nesta etapa, você aprenderá a distinguir e trabalhar com caminhos absolutos e relativos.

  • Um caminho absoluto começa a partir da raiz do sistema de arquivos (por exemplo, / no Linux ou C:\ no Windows).
  • Um caminho relativo é definido a partir do diretório de trabalho atual.

O pathlib fornece métodos para verificar se um caminho é absoluto, resolver um caminho para sua forma absoluta e encontrar um caminho relativo a partir de um ponto de partida.

Primeiro, vamos verificar se um caminho é absoluto usando is_absolute().

  1. Substitua o conteúdo de main.py pelo seguinte código:
from pathlib import Path

## Check if paths are absolute
print(f"Path('/') is absolute: {Path('/').is_absolute()}")
print(f"Path('..') is absolute: {Path('..').is_absolute()}")
  1. Execute o script:
python3 main.py

A saída confirma que / é um caminho absoluto, enquanto .. (diretório pai) é relativo.

Path('/') is absolute: True
Path('..') is absolute: False

Em seguida, você pode obter o caminho absoluto completo de um caminho relativo usando resolve(). Isso é útil para converter caminhos relativos como . (diretório atual) ou .. (diretório pai) em sua forma completa e inequívoca.

  1. Substitua o conteúdo de main.py pelo seguinte código:
from pathlib import Path

## Resolve a relative path to its absolute form
## '..' refers to the parent directory of the current working directory
resolved_path = Path('..').resolve()

print(f"Current working directory: {Path.cwd()}")
print(f"Resolved path of '..': {resolved_path}")
  1. Execute o script:
python3 main.py

A saída mostrará o diretório atual e o caminho absoluto resolvido de seu pai.

Current working directory: /home/labex/project
Resolved path of '..': /home/labex

Finalmente, você pode calcular um caminho relativo de um caminho para outro usando relative_to().

  1. Substitua o conteúdo de main.py pelo seguinte código para calcular um caminho relativo:
from pathlib import Path

## Get a relative path from a starting point
## This shows the path to 'passwd' relative to the root directory '/'
relative_path = Path('/etc/passwd').relative_to('/')
print(f"Path to '/etc/passwd' relative to '/': {relative_path}")
  1. Execute o script:
python3 main.py

A saída mostra o caminho relativo ao diretório raiz:

Path to '/etc/passwd' relative to '/': etc/passwd

Validação e Inspeção de Caminhos

Nesta etapa, você aprenderá como verificar as propriedades de um caminho, como se ele existe e se é um arquivo ou um diretório. Você também aprenderá a recuperar metadados de arquivos, como seu tamanho.

Uma tarefa comum é verificar se um arquivo ou diretório existe antes de tentar acessá-lo. O método exists() é perfeito para isso.

  1. Abra main.py e substitua seu conteúdo pelo seguinte código. Isso verificará a existência do diretório atual (.), de um arquivo (setup.py) e de um arquivo inexistente.
from pathlib import Path

## Check if paths exist
print(f"Path('.') exists: {Path('.').exists()}")
print(f"Path('setup.py') exists: {Path('setup.py').exists()}")
print(f"Path('nonexistentfile') exists: {Path('nonexistentfile').exists()}")
  1. Execute o script:
python3 main.py

A saída confirmará quais caminhos existem:

Path('.') exists: True
Path('setup.py') exists: True
Path('nonexistentfile') exists: False

Você também pode ser mais específico e verificar se um caminho aponta para um arquivo com is_file() ou para um diretório com is_dir().

  1. Substitua o conteúdo de main.py pelo seguinte código para realizar essas verificações:
from pathlib import Path

## Check if a path is a file
print(f"Path('setup.py').is_file(): {Path('setup.py').is_file()}")
print(f"Path('/home').is_file(): {Path('/home').is_file()}")

## Check if a path is a directory
print(f"Path('/').is_dir(): {Path('/').is_dir()}")
print(f"Path('setup.py').is_dir(): {Path('setup.py').is_dir()}")
  1. Execute o script:
python3 main.py

A saída identificará corretamente o tipo de cada caminho:

Path('setup.py').is_file(): True
Path('/home').is_file(): False
Path('/').is_dir(): True
Path('setup.py').is_dir(): False

Finalmente, para obter informações detalhadas do arquivo, como seu tamanho, você pode usar o método stat(). Ele retorna um objeto com vários atributos, incluindo st_size para o tamanho do arquivo em bytes.

  1. Substitua o conteúdo de main.py pelo seguinte código para obter o tamanho de um arquivo do sistema:
from pathlib import Path
import os

## Define the path to a file
file_path = Path('/bin/python3')

## Get file statistics
stat_info = file_path.stat()

## Print the size in bytes
print(f"File: {file_path}")
print(f"Size: {stat_info.st_size} bytes")
  1. Execute o script:
python3 main.py

A saída exibirá o tamanho do executável Python em bytes. O tamanho exato pode variar.

File: /bin/python3
Size: 10024 bytes

Gerenciamento de Arquivos e Pastas

Nesta etapa, você aprenderá a realizar operações comuns do sistema de arquivos, como copiar, mover, renomear e excluir arquivos e pastas, usando o módulo shutil em combinação com pathlib.

O módulo shutil fornece funções de alto nível para operações de arquivo. Vamos começar com a cópia de arquivos. shutil.copy() copia um único arquivo para um novo local.

  1. Abra o arquivo manage_files.py no explorador de arquivos à esquerda.
  2. Substitua o conteúdo de manage_files.py pelo código a seguir. Isso copiará spam.txt para o diretório /tmp/delicious.
import shutil
from pathlib import Path

## Define a origem e o destino
source_file = Path('/tmp/spam.txt')
destination_dir = Path('/tmp/delicious')

## Copia o arquivo
shutil.copy(source_file, destination_dir)

print(f"Copied '{source_file}' to '{destination_dir}'")

## Você também pode renomear o arquivo durante a cópia
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
print("Copied and renamed 'eggs.txt' to 'eggs2.txt'")
  1. Execute o script:
python3 manage_files.py

Para copiar uma árvore de diretórios inteira, use shutil.copytree().

  1. Adicione as seguintes linhas ao final de manage_files.py (anexe estas linhas após o código existente):
## Copia uma árvore de diretórios inteira
shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
print("Copied directory '/tmp/bacon' to '/tmp/bacon_backup'")

Nota: Se você executar este script várias vezes, shutil.copytree() falhará porque o diretório de destino já existe. Este é o comportamento esperado. Por enquanto, execute-o apenas uma vez.

  1. Execute o script novamente para ver em ação.

Em seguida, vamos mover e renomear arquivos com shutil.move(). Esta função pode mover um arquivo para um diretório diferente ou renomeá-lo.

  1. Substitua o conteúdo inteiro de manage_files.py pelo código a seguir para mover bacon.txt:
import shutil
from pathlib import Path

## Cria o diretório de destino se ele não existir
Path('/tmp/eggs').mkdir(parents=True, exist_ok=True)

## Move um arquivo para um diretório (somente se a origem existir)
bacon_source = Path('/tmp/bacon.txt')
if bacon_source.exists():
    shutil.move('/tmp/bacon.txt', '/tmp/eggs')
    print("Moved '/tmp/bacon.txt' to '/tmp/eggs'")
else:
    print("'/tmp/bacon.txt' not found (may have been moved already)")

## Move e renomeia um arquivo
## Primeiro, vamos recriar o arquivo para mover
Path('/tmp/bacon.txt').touch()
shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
print("Moved and renamed '/tmp/bacon.txt' to '/tmp/eggs/new_bacon.txt'")

Nota: O código verifica se o arquivo de origem existe antes de movê-lo, para que você possa executar este script várias vezes com segurança. A segunda parte sempre criará um novo arquivo e o moverá.

  1. Execute o script.

Finalmente, vamos ver a exclusão de arquivos e pastas.

  • Path.unlink() exclui um único arquivo.
  • Path.rmdir() exclui um diretório vazio.
  • shutil.rmtree() exclui um diretório e todo o seu conteúdo.
  1. Substitua o conteúdo inteiro de manage_files.py pelo seguinte:
from pathlib import Path
import shutil

## Cria um arquivo para excluir
file_to_delete = Path('/tmp/deleteme.txt')
file_to_delete.touch()
print(f"Created '{file_to_delete}'")

## Exclui o arquivo
file_to_delete.unlink()
print(f"Deleted '{file_to_delete}'")

## Cria um diretório para excluir
dir_to_delete = Path('/tmp/temp_dir')
dir_to_delete.mkdir(exist_ok=True)
## rmdir() só funciona em diretórios vazios
dir_to_delete.rmdir()
print(f"Deleted empty directory '{dir_to_delete}'")

## Usa shutil.rmtree() para excluir um diretório e seu conteúdo
## Nota: Isso falhará se o diretório não existir, o que é aceitável para este exemplo
if Path('/tmp/bacon_backup').exists():
    shutil.rmtree('/tmp/bacon_backup')
    print("Deleted directory '/tmp/bacon_backup' and all its contents")
else:
    print("Directory '/tmp/bacon_backup' does not exist (may have been deleted already)")

Nota: Se você executar este script várias vezes, algumas operações podem falhar porque arquivos ou diretórios já foram excluídos. Isso é normal e demonstra que essas operações não são idempotentes (não podem ser repetidas com segurança).

  1. Execute o script para realizar as operações de exclusão.

Travessia de Diretórios

Nesta etapa, você aprenderá como listar o conteúdo de um diretório e percorrer recursivamente uma árvore de diretórios.

Para listar o conteúdo de um diretório, você pode usar o método iterdir(), que retorna um iterador de todos os itens (arquivos e diretórios) no caminho especificado.

  1. Primeiro, vamos configurar uma estrutura de diretório de exemplo. Execute este comando no terminal:
mkdir -p ~/project/data/level1 && touch ~/project/data/file0.txt ~/project/data/level1/file1.txt
  1. Agora, abra main.py e substitua seu conteúdo pelo código a seguir para listar o conteúdo do diretório /usr/bin.
from pathlib import Path

## Listar conteúdo de um diretório
print("Conteúdo de '/usr/bin':")
count = 0
for f in Path('/usr/bin').iterdir():
    if count < 5: ## Imprime os 5 primeiros itens para manter a saída curta
        print(f)
        count += 1
    else:
        break
  1. Execute o script:
python3 main.py

A saída mostrará os primeiros arquivos e diretórios dentro de /usr/bin. A lista exata pode variar.

Contents of '/usr/bin':
/usr/bin/[
/usr/bin/addpart
/usr/bin/apt
/usr/bin/apt-cache
/usr/bin/apt-cdrom

Você pode combinar iterdir() com stat() para calcular o tamanho total de todos os arquivos em um diretório.

  1. Substitua o conteúdo de main.py pelo código a seguir. Observe que este código soma apenas os tamanhos dos filhos diretos e não recursa para subdiretórios.
from pathlib import Path

total_size = 0
directory = Path('/usr/bin')

for sub_path in directory.iterdir():
    if sub_path.is_file(): ## Garante que apenas o tamanho dos arquivos seja adicionado
        total_size += sub_path.stat().st_size

print(f"Tamanho total dos arquivos em '{directory}': {total_size} bytes")
  1. Execute o script. Ele imprimirá o tamanho total de todos os arquivos diretamente dentro de /usr/bin.

Para tarefas mais complexas, você pode precisar "percorrer" (walk) uma árvore de diretórios inteira, visitando todos os subdiretórios e arquivos. O método rglob() é perfeito para isso. Ele encontra recursivamente todos os caminhos que correspondem a um padrão específico. Usar * como padrão corresponde a tudo.

  1. Primeiro, crie uma estrutura de diretório mais complexa para este exemplo. Execute isto no terminal:
mkdir -p /tmp/delicious/cats /tmp/delicious/walnut/waffles
touch /tmp/delicious/spam.txt /tmp/delicious/cats/catnames.txt /tmp/delicious/walnut/waffles/butter.txt

Nota: Você pode executar esses comandos várias vezes com segurança. O comando mkdir -p não falhará se os diretórios já existirem, e touch atualizará os carimbos de data/hora dos arquivos se eles já existirem.

  1. Substitua o conteúdo de main.py pelo código a seguir para percorrer o diretório /tmp/delicious:
from pathlib import Path

p = Path('/tmp/delicious')
print(f"Percorrendo a árvore de diretórios para: {p}")

for i in p.rglob('*'):
    print(i)
  1. Execute o script:
python3 main.py

A saída listará todos os arquivos e pastas dentro de /tmp/delicious e seus subdiretórios, demonstrando como rglob() percorre toda a árvore.

Walking directory tree for: /tmp/delicious
/tmp/delicious/spam.txt
/tmp/delicious/eggs2.txt
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/cats/catnames.txt
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt

Resumo

Parabéns por completar o laboratório! Você aprendeu a gerenciar caminhos de arquivos e diretórios de forma eficaz em Python usando o módulo moderno pathlib.

Ao longo deste laboratório, você praticou:

  • Criar e juntar caminhos de maneira multiplataforma usando objetos Path e o operador /.
  • Obter o diretório de trabalho atual e criar diretórios aninhados com mkdir(parents=True).
  • Diferenciar entre caminhos absolutos e relativos e converter entre eles usando is_absolute(), resolve() e relative_to().
  • Validar caminhos com exists(), is_file() e is_dir(), e inspecionar metadados de arquivos com stat().
  • Realizar tarefas essenciais de gerenciamento de arquivos, como copiar, mover e excluir, com o módulo shutil e métodos do pathlib.
  • Percorrer estruturas de diretórios para listar conteúdos com iterdir() e encontrar itens recursivamente com rglob().

Essas habilidades são fundamentais para escrever aplicações Python robustas e de fácil manutenção que interagem com o sistema de arquivos.