Vai al contenuto principale

Guida completa all'uso di pathlib in Python per la manipolazione del file system

Scopri i vantaggi di Python pathlib rispetto al modulo os esplorando oggetti path, componenti del percorso e operazioni comuni sui percorsi per interagire con il file system.
· 9 min leggi

Scopri con l'IA

Fino a poco tempo fa, la manipolazione del file system in Python era notoriamente difficile. Spesso gli sviluppatori inciampavano in percorsi file errati, soggetti a errori perché richiedevano lunghe stringhe come input. Inoltre, si lamentavano di codice che si rompeva di frequente a causa di incongruenze tra diversi sistemi operativi.

Per fortuna, con Python 3.4, il modulo pathlib è stato introdotto nella libreria standard. pathlib offre una soluzione elegante per gestire i percorsi del file system con un approccio a oggetti atteso da tempo e garantisce anche un comportamento indipendente dalla piattaforma.

In questo tutorial completo imparerai le funzionalità del modulo pathlib per supportare le interazioni quotidiane con il tuo file system. Con pathlib beneficerai di flussi di lavoro efficienti e di un recupero dei dati semplice. pathlib è maturato notevolmente negli anni e noi ci siamo tenuti aggiornati così che non debba farlo tu. Iniziamo.

Modulo os di Python vs. pathlib

Prima di Python 3.4, il modo più tradizionale di gestire i percorsi dei file era usare il modulo os. Sebbene il modulo os sia stato a lungo molto efficace, ha iniziato a mostrare la sua età.

Possiamo mostrare il valore unico di pathlib considerando un'attività comune in data science: come trovare tutti i file png dentro una cartella e in tutte le sue sottocartelle.

Se usassimo il modulo os, potremmo scrivere il seguente codice:

import os
dir_path = "/home/user/documents"
files = [
os.path.join(dir_path, f)
for f in os.listdir(dir_path)
if os.path.isfile(os.path.join(dir_path, f)) and f.endswith(".png")
]

Anche se questo codice risolve l'attività immediata di trovare i file png, rivela diversi importanti svantaggi del modulo os. Per cominciare, il codice è lungo e quasi illeggibile, il che è un peccato, considerando che si tratta di un'operazione relativamente semplice. In secondo luogo, il nostro codice presuppone la conoscenza delle list comprehensions, che non si dovrebbero dare per scontate. In terzo luogo, il codice prevede operazioni su stringhe, soggette a errori. E oltre a tutto questo, il codice non è molto conciso. 

Se invece usassimo il modulo pathlib, il nostro codice sarebbe molto più semplice. Come abbiamo accennato, pathlib fornisce un approccio orientato agli oggetti per gestire i percorsi del file system. Guarda qui:

from pathlib import Path

# Create a path object
dir_path = Path(dir_path)

# Find all text files inside a directory
files = list(dir_path.glob("*.png"))

Questa programmazione orientata agli oggetti organizza il codice attorno agli oggetti e alle loro interazioni, portando a codice più modulare, riutilizzabile e manutenibile. Se non hai familiarità con la programmazione a oggetti, vale la pena impararla con il nostro corso di Programmazione a Oggetti in Python. 

Lavorare con gli oggetti Path in Python

La libreria pathlib ruota attorno a quelli che sono chiamati oggetti Path, che rappresentano i percorsi del file system in modo strutturato e indipendente dalla piattaforma.

All'inizio di questo tutorial, abbiamo importato la classe Path dal modulo pathlib nello spazio dei nomi corrente usando la seguente riga di codice:

from pathlib import Path

Dopo aver importato la classe Path da pathlib, possiamo creare oggetti Path in diversi modi, tra cui da stringhe, da altri oggetti Path, dalla directory di lavoro corrente e dalla home directory.

Vediamoli uno per uno.

Creare oggetti path da stringhe

Possiamo creare un oggetto Path passando a una variabile una stringa che rappresenta un percorso del file system. Questo converte la rappresentazione testuale del percorso del file in un oggetto Path.

file_path_str = "data/union_data.csv"

data_path = Path(file_path_str)

Creare oggetti path da altri oggetti path

Gli oggetti Path esistenti possono fungere da mattoni per creare nuovi percorsi. 

Lo facciamo combinando un percorso base, una cartella dei dati e un nome file in un unico percorso. Dobbiamo ricordarci di usare la barra obliqua dove appropriato per estendere i nostri oggetti Path.

base_path = Path("/home/user")
data_dir = Path("data")

# Combining multiple paths
file_path = base_path / data_dir / "prices.csv"  
print(file_path)
'/home/user/data/prices.csv'

Creare oggetti path dalla directory di lavoro corrente

Qui assegniamo la directory di lavoro corrente alla variabile cwd usando il metodo Path.cwd(). Possiamo quindi recuperare il percorso della directory di lavoro corrente in cui sta girando il nostro script. 

cwd = Path.cwd()

print(cwd)
'/home/bexgboost/articles/2024/4_april/8_pathlib'

Creare oggetti path dalla home directory

Possiamo costruire un percorso combinando la nostra home directory con sotto-directory aggiuntive. Qui stiamo unendo la home con le sottodirectory "downloads" e "projects". 

home = Path.home()

home / "downloads" / "projects"
PosixPath('/home/bexgboost/downloads/projects')

Nota importante: La classe Path di per sé non esegue operazioni sul file system come la validazione dei percorsi o la creazione di directory o file. È progettata per rappresentare e manipolare i percorsi. Per interagire effettivamente con il file system (verificare l'esistenza, leggere/scrivere file), dovremo usare metodi specifici degli oggetti Path e, per alcuni casi avanzati, farci aiutare dal modulo os.

Lavorare con i componenti di un percorso in Python

Gli attributi di un percorso file sono varie proprietà e componenti che aiutano a identificare e gestire file e cartelle all'interno di un file system. Proprio come un indirizzo fisico ha parti diverse, come numero civico, città, paese e CAP, anche un percorso del file system può essere scomposto in componenti più piccoli. pathlib ci permette di accedere e manipolare questi componenti usando gli attributi del percorso tramite dot notation.

Lavorare con la directory root

La root è la directory più alta in un file system. Nei sistemi Unix-like, è rappresentata da una barra (/). In Windows, è in genere una lettera di unità seguita da due punti, come C:.

image_file = home / "downloads" / "midjourney.png"

image_file.root
'/'

Lavorare con la directory genitore

La directory genitore contiene il file o la cartella correnti. Si trova un livello sopra rispetto alla directory o al file attuali. 

image_file.parent
PosixPath('/home/bexgboost/downloads')

Lavorare con il nome del file

Questo attributo restituisce l'intero nome del file, inclusa l'estensione, come stringa.

image_file.name
'midjourney.png'

Lavorare con il suffisso del file

L'attributo suffix restituisce l'estensione del file, incluso il punto, come stringa (o una stringa vuota se non c'è estensione).

image_file.suffix
'.png'

Lavorare con lo stem del file

Lo stem restituisce il nome del file senza estensione. Lavorare con lo stem può essere utile quando si convertono file in formati diversi.

image_file.stem
'midjourney'

Nota: Su Mac, i percorsi dei file sono case-sensitive, quindi /Users/username/Documents e /users/username/documents sarebbero diversi. 

L'attributo parts di pathlib

Possiamo usare l'attributo .parts per dividere un oggetto Path nei suoi componenti.

image_file.parts
('/', 'home', 'bexgboost', 'downloads', 'midjourney.png')

L'attributo parents di pathlib

L'attributo parents, che restituisce un generator, trasforma questi componenti in oggetti Path. 

list(image_file.parents)
[PosixPath('/home/bexgboost/downloads'),
PosixPath('/home/bexgboost'),
PosixPath('/home'),
PosixPath('/')]

Operazioni comuni sui percorsi con pathlib

Gli oggetti Path hanno molti metodi che ti consentono di interagire in modo efficiente con cartelle e relativi contenuti. Vediamo come eseguire alcune delle operazioni più comuni.

Elencare le directory

Il metodo iterdir() ti permette di iterare su tutti i file e le sottocartelle in una directory. È particolarmente utile per elaborare tutti i file in una cartella o per eseguire operazioni su ciascuna voce.

cwd = Path.cwd()
 for entry in cwd.iterdir():
# Process the entry here
...
# print(entry)

Dato che iterdir() restituisce un iteratore, le voci vengono recuperate su richiesta man mano che attraversi il ciclo.

Il metodo is_dir()

Il metodo is_dir() restituisce True se il percorso punta a una directory, False altrimenti.

for entry in cwd.iterdir():
   if entry.is_dir():
      print(entry.name)
.ipynb_checkpoints
data
images

Il metodo is_file()

Il metodo .is_file() restituisce True se il percorso punta a un file, False altrimenti.

for entry in cwd.iterdir():
   if entry.is_file():
      print(entry.suffix)
.ipynb
.txt

Il metodo exists()

Dato che gli oggetti Path rappresentano solo percorsi, a volte è necessario verificare se un percorso esiste usando il metodo .exists():

Il metodo .exists() verifica se un percorso esiste. Questo è utile perché gli oggetti Path possono rappresentare file e cartelle che potrebbero non essere effettivamente presenti nel file system.

image_file.exists()
False

Creare ed eliminare percorsi

pathlib offre anche funzionalità per creare ed eliminare file e cartelle. Vediamo come.

Il metodo mkdir()

Il metodo mkdir() crea una nuova directory nel percorso specificato. Per impostazione predefinita, crea la directory nella directory di lavoro corrente.

from pathlib import Path

data_dir = Path("new_data_dir")

# Create the directory 'new_data_dir' in the current working directory
data_dir.mkdir()

Il metodo mkdir(parents=True)

Il metodo mkdir(parents=True) è particolarmente utile quando vuoi creare una struttura di cartelle in cui alcune directory genitrici potrebbero non esistere. Impostando parents=True ti assicuri che tutte le cartelle necessarie vengano create lungo il percorso.

sub_dir = Path("data/nested/subdirectory")

# Create 'data/nested/subdirectory', even if 'data' or 'nested' don't exist
sub_dir.mkdir(parents=True)

Tieni presente che mkdir() solleva un'eccezione se esiste già una directory con lo stesso nome.

Path('data').mkdir()
FileExistsError: [Errno 17] File exists: 'data'

Il metodo unlink()

Il metodo unlink() elimina in modo permanente un file rappresentato dall'oggetto Path. Si consiglia di verificare se un file esiste prima di eseguire questo metodo per evitare di ricevere un errore.

to_delete = Path("data/prices.csv")

if to_delete.exists():
   to_delete.unlink()
   print(f"Successfully deleted {to_delete.name}")
Successfully deleted prices.csv

Il metodo rmdir()

Il metodo rmdir() rimuove una directory vuota. Ricorda che rmdir() funziona solo per rimuovere directory vuote. Il modo più semplice per eliminare una directory non vuota è usare la libreria shutil o il terminale.

empty_dir = Path("new_data_dir")

empty_dir.rmdir()

Nota: Fai attenzione quando usi unlink() o rmdir() perché i loro risultati sono permanenti.

Manipolazione avanzata dei percorsi

Passiamo ad alcuni concetti avanzati di manipolazione dei percorsi e a come applicarli in pratica usando pathlib.

Percorsi relativi vs. assoluti

Inizieremo comprendendo le differenze tra percorsi assoluti e relativi, dato che ricorrono spesso.

Percorsi relativi

I percorsi relativi specificano la posizione di un file o di una cartella rispetto alla directory corrente, da cui il termine relativi. Sono brevi e flessibili all'interno del tuo progetto ma possono creare confusione se cambi la directory di lavoro.

Per esempio, ho una cartella images nella mia directory di lavoro corrente, che contiene il file midjourney.png.

image = Path("images/midjourney.png")

image
PosixPath('images/midjourney.png')

Il codice sopra funziona ora, ma se sposto il notebook che sto usando in un'altra posizione, lo snippet si romperà perché la cartella images non si è spostata insieme al notebook.

Percorsi assoluti

I percorsi assoluti specificano la posizione completa di un file o di una cartella a partire dalla root del file system. Sono indipendenti dalla directory corrente e offrono un punto di riferimento chiaro per qualsiasi utente ovunque nel sistema.

image_absolute = Path("/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png")

image_absolute
PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')

Come puoi vedere, i percorsi assoluti possono essere piuttosto lunghi, soprattutto in progetti complessi con strutture ad albero annidate. Per questo motivo, la maggior parte delle persone preferisce i percorsi relativi, che sono più brevi.

Metodo resolve

pathlib fornisce metodi per convertire i percorsi relativi in assoluti con il metodo resolve().

relative_image = Path("images/midjourney.png")

absolute_image = relative_image.resolve()

absolute_image
PosixPath('/home/bexgboost/articles/2024/4_april/8_pathlib/images/midjourney.png')

Possiamo anche fare il contrario: se abbiamo un percorso assoluto, possiamo convertirlo in un percorso relativo basato su una directory di riferimento.

relative_path = Path.cwd()

absolute_image.relative_to(relative_path)
PosixPath('images/midjourney.png')

Globbing

Per illustrare il globbing, possiamo tornare all'esempio introdotto all'inizio dell'articolo, dove abbiamo scritto del codice per trovare tutti i file png in una data cartella.

files = list(dir_path.glob("*.png"))

pathlib usa il modulo integrato .glob() per cercare in modo efficiente file che corrispondono a un pattern specifico in qualsiasi cartella. Questo modulo è molto utile quando si elaborano file con nomi o estensioni simili.

Il metodo glob funziona accettando come input una stringa pattern contenente wildcard e restituisce un oggetto generatore che produce su richiesta oggetti Path corrispondenti:

  • *: Corrisponde a zero o più caratteri.

  • ?: Corrisponde a un singolo carattere qualsiasi.

  • []: Corrisponde a un intervallo di caratteri racchiusi tra parentesi (ad es., [a-z] corrisponde a qualsiasi lettera minuscola).

Per illustrare, proviamo a trovare tutti i notebook Jupyter nella mia cartella articles.

articles_dir = Path.home() / "articles"

# Find all scripts
notebooks = articles_dir.glob("*.ipynb")

# Print how many found
print(len(list(notebooks)))
0

Il metodo .glob() non ha trovato alcun notebook, il che a prima vista sembra sorprendente perché ho scritto oltre 150 articoli. Il motivo è che .glob() cerca solo all'interno della cartella specificata, non nelle sue sottocartelle.

Possiamo risolvere facendo una ricerca ricorsiva, per cui dobbiamo usare il metodo rglob(), che ha una sintassi simile:

notebooks = articles_dir.rglob("*.ipynb")

print(len(list(notebooks)))
357

Questa volta, il nostro codice ha trovato tutti e 357 i file.

Lavorare con i file

Come abbiamo visto, gli oggetti Path rappresentano solo i file ma non eseguono operazioni su di essi. Tuttavia, dispongono di alcuni metodi per operazioni comuni sui file. Vedremo come usarli in questa sezione.

Lettura dei file

Leggere il contenuto dei file è un'operazione fondamentale in molte applicazioni Python. pathlib fornisce metodi abbreviati pratici per leggere i file come testo o come byte grezzi.

Il metodo read_text() ci permette di leggere il contenuto di un file di testo e di chiuderlo.

file = Path("file.txt")

print(file.read_text())
This is sample text.

Per i file binari, possiamo invece usare il metodo read_bytes().

image = Path("images/midjourney.png")

image.read_bytes()[:10]
b'\x89PNG\r\n\x1a\n\x00\x00'

Ricorda, quando usi un metodo read_*, la gestione degli errori è importante:

nonexistent_file = Path("gibberish.txt")

try:
   contents = nonexistent_file.read_text()
except FileNotFoundError:
   print("No such thing.")
No such thing.

Scrittura dei file

Scrivere su file è facile quanto leggerli. Per scrivere file, abbiamo il metodo write_text().

file = Path("file.txt")

file.write_text("This is new text.")
17
file.read_text()
'This is new text.'

Come possiamo vedere, il metodo write_text() sovrascrive il testo. Anche se non esiste una modalità append per write_text(), possiamo usare insieme read_text() e write_text() per aggiungere testo alla fine del file.

old_text = file.read_text() + "\n"
final_text = "This is the final text."

# Combine old and new texts and write them back
file.write_text(old_text + final_text)

print(file.read_text())
This is new text.

This is the final text.

write_bytes() funziona in modo simile. Per illustrare, duplichiamo prima l'immagine midjourney.png con un nuovo nome.

original_image = Path("images/midjourney.png")

new_image = original_image.with_stem("duplicated_midjourney")

new_image
PosixPath('images/duplicated_midjourney.png')

Il metodo with_stem() restituisce un percorso file con un nome diverso (anche se il suffisso rimane lo stesso). Questo ci permette di leggere un'immagine originale e scriverne il contenuto in una nuova immagine. 

new_image.write_bytes(original_image.read_bytes())
1979612

Rinominare e spostare file

Oltre alla funzione with_stem() per rinominare lo stem di un file, pathlib offre il metodo rename() per una rinomina più completa. 

file = Path("file.txt")

target_path = Path("new_file.txt")

file.rename(target_path)
PosixPath('new_file.txt')

rename() accetta un percorso di destinazione, che può essere una stringa o un altro oggetto path.

Per spostare i file, puoi usare la funzione replace(), che accetta anch'essa un percorso di destinazione:

# Define the file to be moved
source_file = Path("new_file.txt")

# Define the location to put the file
destination = Path("data/new/location")

# Create the directories if they don't exist
destination.mkdir(parents=True)

# Move the file
source_file.replace(destination / source_file.name)
PosixPath('data/new/location/new_file.txt')

Creare file vuoti

pathlib ci permette di creare file vuoti usando il metodo touch:

# Define new file path
new_dataset = Path("data/new.csv")

new_dataset.exists()
False
new_dataset.touch()

new_dataset.exists()
True

Il metodo touch nasce per aggiornare l'orario di modifica di un file, quindi può essere usato anche su file esistenti.

original_image.touch()

Quando hai bisogno di riservare un nome file per un utilizzo futuro ma al momento non hai contenuti da scriverci, puoi usare touch per creare un file vuoto. Il metodo è ispirato al comando touch del terminale Unix.

Permessi e informazioni sul file system

Come ultimo punto, vedremo come accedere alle caratteristiche di un file usando il metodo .stat(). Se conosci os, noterai che questo nuovo metodo ha lo stesso output di os.stat().

image_stats = original_image.stat()

image_stats
os.stat_result(st_mode=33188, st_ino=1950175, st_dev=2080, st_nlink=1, st_uid=1000, st_gid=1000, st_size=1979612, st_atime=1714664562, st_mtime=1714664562, st_ctime=1714664562)

Possiamo anche recuperare la dimensione del file usando la dot notation.

image_size = image_stats.st_size

# File size in megabytes
image_size / (1024**2)
1.8879051208496094

Conclusione

L'introduzione del modulo pathlib in Python 3.4 ha semplificato notevolmente la manipolazione del file system per gli sviluppatori. Offrendo un approccio orientato agli oggetti alla gestione dei percorsi, pathlib fornisce un modo strutturato e lineare per rappresentare i percorsi del file system. pathlib offre anche indipendenza dalla piattaforma, il che significa che gestisce i separatori di percorso in modo coerente tra diversi sistemi operativi così che il nostro codice non si rompa su una nuova macchina. Infine, pathlib mette a disposizione un ampio set di metodi concisi ed espressivi per le operazioni comuni sul file system, come abbiamo visto. 

Tieni presente che pathlib è solo una delle tante potenti librerie integrate in Python. Seguendo i nostri corsi Data Scientist With Python Career Track, Python Programming Skill Track e Intro to Python for Data Science, padroneggerai un ampio set di librerie integrate per diventare un solido programmatore Python. 

Grazie per la lettura!


Bexruz (Bex) Tuychiev

Autore

Bexruz (Bex) Tuychiev

Sono un creator di contenuti sulla data science con oltre 2 anni di esperienza e uno dei più seguiti su Medium. Mi piace scrivere articoli dettagliati su AI e ML con un pizzico di sarcasmo, perché bisogna pur fare qualcosa per renderli un po’ meno noiosi. Ho pubblicato oltre 130 articoli e un corso su DataCamp, con un altro in arrivo. I miei contenuti sono stati visti da oltre 5 milioni di persone, 20.000 delle quali sono diventate follower sia su Medium che su LinkedIn. 

Argomenti

Correlato