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

Para um mergulho profundo em operações práticas do sistema de arquivos, confira nossa postagem no blog: 10 Operações Essenciais do Sistema de Arquivos que Todo Desenvolvedor Deve Conhecer.

Existem dois módulos principais em Python que lidam com a manipulação de caminhos. Um é o módulo os.path e o outro é o módulo pathlib.

Pathlib vs Módulo OS

pathlib fornece muito mais funcionalidade do que as listadas acima, como obter o nome do arquivo, obter a extensão do arquivo, ler/escrever um arquivo sem abri-lo manualmente, etc. Consulte a documentação oficial se você pretende saber mais.

Caminhos Linux e Windows

No Windows, os caminhos são escritos usando barras invertidas (\) como separador entre nomes de pastas. Em sistemas operacionais baseados em Unix, como macOS, Linux e BSDs, a barra normal (/) é usada como separador de caminho. Juntar caminhos pode ser um aborrecimento se o seu código precisar funcionar em diferentes plataformas.

Felizmente, o módulo pathlib do Python oferece uma maneira fácil de lidar com isso.

Usando pathlib em *nix:

# pathlib.Path: manipulação de caminho multiplataforma
from pathlib import Path

print(Path('usr').joinpath('bin').joinpath('spam'))  # Junta componentes do caminho
usr/bin/spam

pathlib também fornece um atalho para joinpath usando o operador /:

# Operador Path (/): maneira conveniente de juntar caminhos (multiplataforma)
from pathlib import Path

print(Path('usr') / 'bin' / 'spam')  # Usa o operador / em vez de joinpath()
usr/bin/spam

Note que o separador de caminho é diferente entre Windows e sistemas operacionais baseados em Unix, é por isso que você deseja usar pathlib em vez de adicionar strings juntas para juntar caminhos.

Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

Qual é a maneira correta de juntar caminhos usando pathlib em Python?
A. Path('usr') + 'bin' + 'spam'
B. Path('usr') / 'bin' / 'spam'
C. Path('usr').join('bin').join('spam')
D. Path('usr/bin/spam')

Juntar caminhos é útil se você precisar criar caminhos de arquivo diferentes sob o mesmo diretório.

Usando pathlib em *nix:

# Path.home(): obter o diretório inicial do usuário, combinar com nomes de arquivos
my_files = ['accounts.txt', 'details.csv', 'invite.docx']
home = Path.home()  # Obtém o caminho do diretório inicial
for filename in my_files:
    print(home / filename)  # Combina o caminho inicial com cada nome de arquivo
/home/labex/project/accounts.txt
/home/labex/project/details.csv
/home/labex/project/invite.docx

Expandindo o diretório inicial do usuário

Usando os.path.expanduser() para expandir ~ para o diretório inicial do usuário:

import os.path

# Expande ~ para o diretório inicial do usuário
print(os.path.expanduser('~'))
/home/labex/project
# Expande ~/Documents para o caminho completo
print(os.path.expanduser('~/Documents'))
/home/labex/project/Documents
# Funciona com caminhos contendo ~
print(os.path.expanduser('~/myfile.txt'))
/home/labex/project/myfile.txt

O diretório de trabalho atual

Você pode obter o diretório de trabalho atual usando pathlib:

# Path.cwd(): obter o diretório de trabalho atual
from pathlib import Path

print(Path.cwd())  # Retorna o diretório de trabalho atual como um objeto Path
/home/labex/project

Criação de novas pastas

Usando pathlib em *nix:

from pathlib import Path
cwd = Path.cwd()
(cwd / 'delicious' / 'walnut' / 'waffles').mkdir()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/pathlib.py", line 1226, in mkdir
    self._accessor.mkdir(self, mode)
  File "/usr/lib/python3.6/pathlib.py", line 387, in wrapped
    return strfunc(str(pathobj), *args)
FileNotFoundError: [Errno 2] No such file or directory: '/home/labex/project/delicious/walnut/waffles'

Oh não, recebemos um erro chato! O motivo é que o diretório ‘delicious’ não existe, então não podemos criar os diretórios ‘walnut’ e ‘waffles’ sob ele. Para corrigir isso, faça:

# mkdir(parents=True): cria o diretório e todos os diretórios pais, se necessário
from pathlib import Path
cwd = Path.cwd()
(cwd / 'delicious' / 'walnut' / 'waffles').mkdir(parents=True)  # Cria diretórios aninhados

E tudo fica bem :)

Caminhos Absolutos vs. Relativos

Existem duas maneiras de especificar um caminho de arquivo.

  • Um caminho absoluto, que sempre começa com a pasta raiz
  • Um caminho relativo, que é relativo ao diretório de trabalho atual do programa

Existem também as pastas ponto (.) e ponto-ponto (..). Estas não são pastas reais, mas nomes especiais que podem ser usados em um caminho. Um único ponto (“ponto”) para um nome de pasta é um atalho para “este diretório”. Dois pontos (“ponto-ponto”) significam “a pasta pai”.

Manipulação de caminhos absolutos

Para ver se um caminho é um caminho absoluto usando pathlib:

from pathlib import Path
Path('/').is_absolute()
True
Path('..').is_absolute()
False
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

O que Path('/').is_absolute() retorna?
A. True
B. False
C. None
D. '/'

Você pode extrair um caminho absoluto com pathlib:

from pathlib import Path
print(Path.cwd())
/home/labex/project
print(Path('..').resolve())
/home

Manipulação de caminhos relativos

Você pode obter um caminho relativo de um caminho inicial para outro caminho usando pathlib:

from pathlib import Path
print(Path('/etc/passwd').relative_to('/'))
etc/passwd

Validade de Caminho e Arquivo

Verificando se um arquivo/diretório existe

Usando pathlib em *nix:

from pathlib import Path

Path('.').exists()
True
Path('setup.py').exists()
True
Path('/etc').exists()
True
Path('nonexistentfile').exists()
False

Verificando se um caminho é um arquivo

Usando pathlib em *nix:

from pathlib import Path

Path('setup.py').is_file()
True
Path('/home').is_file()
False
Path('nonexistentfile').is_file()
False
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

O que Path('setup.py').is_file() retornará se setup.py existir?
A. 'setup.py'
B. False
C. True
D. None

Verificando se um caminho é um diretório

Usando pathlib em *nix:

from pathlib import Path

Path('/').is_dir()
True
Path('setup.py').is_dir()
False
Path('/spam').is_dir()
False

Obtendo o tamanho de um arquivo em bytes

Usando pathlib em *nix:

from pathlib import Path

stat = Path('/bin/python3.6').stat()
print(stat) # stat contém algumas outras informações sobre o arquivo também
os.stat_result(st_mode=33261, st_ino=141087, st_dev=2051, st_nlink=2, st_uid=0,
--snip--
st_gid=0, st_size=10024, st_atime=1517725562, st_mtime=1515119809, st_ctime=1517261276)
print(stat.st_size) # tamanho em bytes
10024

Listando diretórios

Listando o conteúdo do diretório usando pathlib em *nix:

from pathlib import Path

for f in Path('/usr/bin').iterdir():
    print(f)
...
/usr/bin/tiff2rgba
/usr/bin/iconv
/usr/bin/ldd
/usr/bin/cache_restore
/usr/bin/udiskie
/usr/bin/unix2dos
/usr/bin/t1reencode
/usr/bin/epstopdf
/usr/bin/idle3
...

Tamanhos de arquivos de diretório

AVISO

Diretórios também têm um tamanho! Portanto, você pode querer verificar se um caminho é um arquivo ou diretório usando os métodos discutidos na seção acima.

Usando pathlib em *nix:

from pathlib import Path

total_size = 0
for sub_path in Path('/usr/bin').iterdir():
    total_size += sub_path.stat().st_size

print(total_size)
1903178911

Copiando arquivos e pastas

O módulo shutil fornece funções para copiar arquivos, bem como pastas inteiras.

import shutil

shutil.copy('/tmp/spam.txt', '/tmp/delicious')
/tmp/delicious/spam.txt
shutil.copy('/tmp/eggs.txt', '/tmp/delicious/eggs2.txt')
/tmp/delicious/eggs2.txt
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

Qual função você deve usar para copiar uma árvore de diretórios inteira, incluindo todos os subdiretórios e arquivos?
A. shutil.copy()
B. Path.copy()
C. os.copy()
D. shutil.copytree()

Enquanto shutil.copy() copiará um único arquivo, shutil.copytree() copiará uma pasta inteira e todas as pastas e arquivos contidos nela:

import shutil

shutil.copytree('/tmp/bacon', '/tmp/bacon_backup')
/tmp/bacon_backup

Movendo e Renomeando

import shutil

shutil.move('/tmp/bacon.txt', '/tmp/eggs')
/tmp/eggs/bacon.txt

O caminho de destino também pode especificar um nome de arquivo. No exemplo a seguir, o arquivo de origem é movido e renomeado:

shutil.move('/tmp/bacon.txt', '/tmp/eggs/new_bacon.txt')
/tmp/eggs/new_bacon.txt

Se não houver pasta eggs, então move() renomeará bacon.txt para um arquivo chamado eggs:

shutil.move('/tmp/bacon.txt', '/tmp/eggs')
/tmp/eggs

Excluindo arquivos e pastas

  • Chamar Path.unlink() excluirá o arquivo no caminho.
  • Chamar Path.rmdir() excluirá a pasta no caminho. Esta pasta deve estar vazia de quaisquer arquivos ou pastas.
  • Chamar shutil.rmtree(path) removerá a pasta no caminho, e todos os arquivos e pastas que ela contém também serão excluídos.
Quiz

Faça login para responder este quiz e acompanhar seu progresso de aprendizagem

Qual método pode excluir um diretório não vazio e todo o seu conteúdo?
A. Path.rmdir()
B. shutil.rmtree()
C. Path.unlink()
D. os.remove()

Percorrendo uma Árvore de Diretórios

O objeto Path tem um método rglob() para iterar recursivamente sobre arquivos e diretórios.

from pathlib import Path

p = Path('/tmp/delicious')
for i in p.rglob('*'):
    print(i)
/tmp/delicious/cats
/tmp/delicious/walnut
/tmp/delicious/spam.txt
/tmp/delicious/cats/catnames.txt
/tmp/delicious/cats/zophie.jpg
/tmp/delicious/walnut/waffles
/tmp/delicious/walnut/waffles/butter.txt