pathlib - Percorsi di Filesystem come Oggetti
Scopo: Elabora, costruisce, verifica oppure lavora con nomi di file e percorsi usando una API orientata agli oggetti invece che operazioni di stringa a basso livello.
Rappresentazione di Percorsi
Il modulo pathlib include classi per gestire percorsi di filesystem formattati usando sia la sintassi POSIX standard che quella Microsoft Windows. Comprende le cosiddette classi "pure", che operano sulle stringhe ma non interagiscono con il filesystem reale, e quelle "concrete", le quali estendono l'API per includere operazioni che riflettono o modificano dati sul filesystem locale.
Le classi pure PurePosixPath
e PureWindowsPath
possono essere istanziate e usate su ogni sistema operativo, visto che funzionano solo con i nomi. Per istanziare la classe corretta per lavorare con un filesystem reale, si usi PosixPath
o WindowsPath
a seconda della piattaforma.
Costruire Percorsi
Per istanziare un nuovo percorso, si passa una stringa come primo argomento. La rappresentazione stringa dell'oggetto percorso è quel valore. Per creare un nuovo percorso che faccia riferimento a un valore relativo a un percorso esistente, si usi l'operatore /
per estendere il percorso. L'argomento per l'operatore può essere sia una stringa che un altro oggetto percorso.
# pathlib_operator.py
import pathlib
usr = pathlib.PurePosixPath('/usr')
print(usr)
usr_local = usr / 'local'
print(usr_local)
usr_share = usr / pathlib.PurePosixPath('share')
print(usr_share)
root = usr / '..'
print(root)
etc = root / '/etc/'
print(etc)
Come mostra il valore di root
nel risultato dell'esempio, l'operatore combina i valori di percorso così come sono passati, e non normalizza il risultato quando contiene il riferimento alla directory genitore ".."
. Tuttavia, se un segmento inizia con il separatore di percorso, viene interpretato come un nuovo riferimento a "root" allo stesso modo della funzione os.path.join()
. Separatori di percorso supplementari sono rimessi dal mezzo del valore del percorso, come in etc
in questo esempio.
$ python3 pathlib_operator.py /usr /usr/local /usr/share /usr/.. /etc
Le classi di percorso concrete includono il metodo resolve()
per normalizzare un percorso cercando nel filesystem directory e collegamenti simbolici per produrre il percorso assoluto riferito da un nome.
# pathlib_resolve.py
import pathlib
usr_local = pathlib.Path('/usr/local')
share = usr_local / '..' / 'share'
print(share.resolve())
Qui il percorso relativo è convertito in un percorso assoluto in /usr/share
. Se il percorso in input comprende collegamenti simbolici, sono anch'essi espansi per consentire al percorso di far direttamente riferimento alla destinazione.
$ python3 pathlib_resolve.py /usr/share
Per costruire percorsi quando non si conoscono in anticipo i segmenti, si usi joinpath()
, passando ogni segmento del percorso come argomento separato.
# pathlib_joinpath.py
import pathlib
root = pathlib.PurePosixPath('/')
subdirs = ['usr', 'local']
usr_local = root.joinpath(*subdirs)
print(usr_local)
Come con l'operatore /
la chiamata di joinpath()
crea una nuova istanza.
$ python3 pathlib_joinpath.py /usr/local
Dato un oggetto percorso esistente, è facile costruirne uno nuovo con piccole differenze tipo il fare riferimento a un file diverso nella stessa directory. Si usi with_name()
per creare un nuovo percorso che sostituisce la porzione del nome con un nome di file differente. Si usi with_suffix()
per creare un nuovo percorso che sostituisca l'estensione del nome del file con un diverso valore.
# pathlib_from_existing.py
import pathlib
ind = pathlib.PurePosixPath('source/pathlib/index.rst')
print(ind)
py = ind.with_name('pathlib_from_existing.py')
print(py)
pyc = py.with_suffix('.pyc')
print(pyc)
Entrambi i metodi ritornano nuovi oggetti, e gli originali rimangono invariati.
$ python3 pathlib_from_existing.py source/pathlib/index.rst source/pathlib/pathlib_from_existing.py source/pathlib/pathlib_from_existing.pyc
Analizzare percorsi
Gli oggetti di percorso hanno metodi e proprietà per estrarre valori parziali dal nome. Ad esempio la proprietà parts
produce una sequenza di segmenti di percorso analizzati in base al valore del separatore di percorso.
# pathlib_parts.py
import pathlib
p = pathlib.PurePosixPath('/usr/local')
print(p.parts)
La sequenza è una tupla, che riflette l'immutabilità dell'istanza di percorso.
$ python3 pathlib_parts.py ('/', 'usr', 'local')
Ci sono due modi per risalire la gerarchia del filesystem dato un oggetto percorso. La proprietà parent
fa riferimento da una nuova istanza di percorso per la directory che contiene il percorso, il valore ritornato da os.path.dirname()
. La proprietà parents
è un iterabile che produce riferimenti alla directory genitore, risalendo continuamente la gerarchia del percorso fino a raggiungere la radice.
# pathlib_parents.py
import pathlib
p = pathlib.PurePosixPath('/usr/local/lib')
print('genitore: {}'.format(p.parent))
print('\ngerarchia:')
for up in p.parents:
print(up)
L'esempio itera sulla proprietà parents
e stampa i valori dei membri.
$ python3 pathlib_parents.py genitore: /usr/local gerarchia: /usr/local /usr /
Altre parti del percorso possono essere raggiunte tramite le proprietà dell'oggetto di percorso. La proprietà name
mantiene la parte finale del percorso, dopo l'ultimo separatore di percorso (lo stesso valore prodotto da os.path.basename()
). La proprietà suffix
mantiene il valore che segue il separatore di estensione e la proprietà stem
mantiene la porzione del nome prima del suffisso.
# pathlib_name.py
import pathlib
p = pathlib.PurePosixPath('./source/pathlib/pathlib_name.py')
print('percorso : {}'.format(p))
print('nome : {}'.format(p.name))
print('suffisso : {}'.format(p.suffix))
print('radice nome: {}'.format(p.stem))
Sebbene i valori di suffix
e stem
siano simili a quelli prodotti da os.path.splitext()
, i valori sono basati solo sul valore di name
e non del percorso completo.
$ python3 pathlib_name.py percorso : source/pathlib/pathlib_name.py nome : pathlib_name.py suffisso : .py radice nome: pathlib_name
Creare Percorsi Concreti
Le istanze della classe concreta Path
possono essere create da argomenti stringa in riferimento al nome (o nome potenziale) di un file, directory o collegamento simbolico sul filesystem. La classe fornisce anche parecchi metodi di convenienza per costruire istanze utilizzando locazioni comunemente usate che cambiano, tipo la directory di lavoro corrente e la directory home dell'utente.
# pathlib_convenience.py
import pathlib
home = pathlib.Path.home()
print('home : ', home)
cwd = pathlib.Path.cwd()
print('dir. corrente: ', cwd)
Entrambi i metodi creano istanze di Path
prepopolate con un riferimento assoluto al filesystem.
$ python3 pathlib_convenience.py home : /home/robby dir. corrente: ...
Contenuto delle Directory
Ci sono tre metodi per accedere al contenuto di una directory per trovare i nomi di file disponibili sul filesystem. iterdir()
è un generatore, che trattiene una nuova istanza di Path
per ciascun elemento nella directory interessata.
# pathlib_iterdir.py
import pathlib
p = pathlib.Path('.')
for f in p.iterdir():
print(f)
Se Path
non fa riferimento a una directory, iterdir()
solleva una eccezione NotADirectoryError
.
$ python3 pathlib_iterdir.py pathlib_operator.py pathlib_parents.py pathlib_iterdir.py pathlib_resolve.py pathlib_parts.py pathlib_name.py pathlib_from_existing.py pathlib_convenience.py
Si usi glob()
per trovare solo i file che corrispondono a un modello.
# pathlib_glob.py
import pathlib
p = pathlib.Path('..')
for f in p.glob('*.rst'):
print(f)
Questo esempio mostra tutti i file di tipo reStructuredText nella directory genitore rispetto allo script.
$ python3 pathlib_glob.py ../about.rst ../algorithm_tools.rst ../book.rst ../compression.rst ../concurrency.rst ../cryptographic.rst ../data_structures.rst ../dates.rst ../dev_tools.rst ../email.rst ../file_access.rst ../frameworks.rst ../i18n.rst ../importing.rst ../index.rst ../internet_protocols.rst ../language.rst ../networking.rst ../numeric.rst ../persistence.rst ../porting_notes.rst ../runtime_services.rst ../text.rst ../third_party.rst ../unix.rst
Il processore di glob
supporta la scansione ricorsiva usando il modello con prefisso **
oppure chiamando rglob()
in luogo di glob()
.
# pathlib_rglob.py
import pathlib
p = pathlib.Path('..')
for f in p.rglob('pathlib_*.py'):
print(f)
Visto che questo esempio viene eseguito dalla directory genitore, è richiesta una ricerca ricorsiva per trovare i file di esempio che corrispondono al modello pathlib_*.py
.
$ python3 dumpscripts/pathlib_rglob.py ../pymotw-it3.0/dumpscripts/pathlib_operator.py ../pymotw-it3.0/dumpscripts/pathlib_parents.py ../pymotw-it3.0/dumpscripts/pathlib_iterdir.py ../pymotw-it3.0/dumpscripts/pathlib_resolve.py ../pymotw-it3.0/dumpscripts/pathlib_parts.py ../pymotw-it3.0/dumpscripts/pathlib_name.py ../pymotw-it3.0/dumpscripts/pathlib_rglob.py ../pymotw-it3.0/dumpscripts/pathlib_from_existing.py ../pymotw-it3.0/dumpscripts/pathlib_convenience.py ../pymotw-it3.0/dumpscripts/pathlib_glob.py
Leggere e Scrivere File
Ogni istanza di Path
include metodi per lavorare con il contenuto del file al quale fa riferimento. Per ottenere immediatamente il contenuto, si usi read_bytes()
oppure read_text()
. Per scrivere sul file si usi write_bytes()
oppure write_text()
. Si usi il metodo open()
per aprire il file e trattenerne l'handle. invece che passare il nome alla funzione built-in open()
.
# pathlib_read_write.py
import pathlib
f = pathlib.Path('esempio.txt')
f.write_bytes('Questo è il contenuto'.encode('utf-8'))
with f.open('r', encoding='utf-8') as handle:
print('lettura da open(): {!r}'.format(handle.read()))
print('read_text(): {!r}'.format(f.read_text('utf-8')))
I metodi di convenienza eseguono qualche controllo di tipo prima aprire il file e scriverci sopra, ma per il resto equivale a eseguire l'operazione direttamente.
$ python3 pathlib_read_write.py lettura da open(): 'Questo è il contenuto' read_text(): 'Questo è il contenuto'
Manipolare Directory e Collegamenti Simbolici
I percorsi che rappresentano directory o collegamenti simbolici che non esistono possono essere usati per creare i relativi elementi nel filesystem.
# pathlib_mkdir.py
import pathlib
p = pathlib.Path('dir_esempio')
print('Creazione di {}'.format(p))
p.mkdir()
Se il percorso esiste già, mkdir()
solleva un FileExistsError
.
$ python3 pathlib_mkdir.py Creazione di dir_esempio $ python3 pathlib_mkdir.py Creazione di dir_esempio Traceback (most recent call last): File "pathlib_mkdir.py", line 8, in <module> p.mkdir() File "/usr/lib/python3.5/pathlib.py", line 1214, in mkdir self._accessor.mkdir(self, mode) File "/usr/lib/python3.5/pathlib.py", line 371, in wrapped return strfunc(str(pathobj), *args) FileExistsError: [Errno 17] File exists: 'dir_esempio'
Si usi symlink_to()
per creare un collegamento simbolico. Questo collegamento assumerà un nome in base al valore del percorso e farà riferimento al nome dato come argomento a symlink_to()
.
# pathlib_symlink_to.py
import pathlib
p = pathlib.Path('coll_di_esempio')
p.symlink_to('pathlib_symlink_to.py')
print(p)
print(p.resolve().name)
Questo esempio crea un collegamento simbolico, quindi usa resolve()
per leggere il collegamento per trovare a cosa punta e ne stampa il nome.
$ python3 pathlib_symlink_to.py coll_di_esempio pathlib_symlink_to.py
Tipi di File
Una istanza di Path
include parecchi metodi per verificare il tipo di un file a cui fa riferimento. Questo esempio crea parecchi file di diversi tipi e li verifica assieme ad altri file specifici per il dispositivo disponibili nel sistema operativo locale.
# pathlib_types.py
import itertools
import os
import pathlib
root = pathlib.Path('file_di_prova')
# Pulisce quanto elaborato nelle precedenti esecuzioni
if root.exists():
for f in root.iterdir():
f.unlink()
else:
root.mkdir()
# Crea i file di prova
(root / 'file').write_text(
'Questo è un file normale', encoding='utf-8')
(root / 'symlink').symlink_to('file')
os.mkfifo(str(root / 'fifo'))
# Verifica il tipo di file
to_scan = itertools.chain(
root.iterdir(),
[pathlib.Path('/dev/disk0'),
pathlib.Path('/dev/console')],
)
hfmt = '{:18s}' + (' {:>5}' * 6)
print(hfmt.format('Nome', 'File', 'Dir', 'Link', 'FIFO', 'Blocco',
'Carattere'))
print()
fmt = '{:20s} ' + ('{!r:>5} ' * 6)
for f in to_scan:
print(fmt.format(
str(f),
f.is_file(),
f.is_dir(),
f.is_symlink(),
f.is_fifo(),
f.is_block_device(),
f.is_char_device(),
))
Ognuno dei metodi is_dir()
, is_file()
, is_symlink()
, is_socket()
, is_fifo()
, is_block_device()
e is_char_device()
non richiede argomenti.
$ python3 pathlib_types.py Nome File Dir Link FIFO Blocco Carattere file_di_prova/fifo False False False True False False file_di_prova/symlink True False True False False False file_di_prova/file True False False False False False /dev/disk0 False False False False False False /dev/console False False False False False True
Proprietà dei File
Informazioni dettagliate per un file possono essere recuperate usando i metodi stat()
oppure lstat()
(per verificare lo stato di qualcosa che possa assomigliare a un collegamento simbolico). Questi metodi producono gli stessi risultati di os.stat()
ed os.lstat()
.
# pathlib_stat.py
import pathlib
import sys
import time
if len(sys.argv) == 1:
filename = __file__
else:
filename = sys.argv[1]
p = pathlib.Path(filename)
stat_info = p.stat()
print('{}:'.format(filename))
print(' Dimnsione :', stat_info.st_size)
print(' Permessi :', oct(stat_info.st_mode))
print(' Proprietario :', stat_info.st_uid)
print(' Dispositivo :', stat_info.st_dev)
print(' Creato :', time.ctime(stat_info.st_ctime))
print(' Ultima modifica:', time.ctime(stat_info.st_mtime))
print(' Ultimo accesso :', time.ctime(stat_info.st_atime))
L'output potrà variare in base a dove il codice di esempio viene eseguito. Si provi a passare nomi di file diversi da riga di comando a pathlib_stat.py
.
$ python3 pathlib_stat.py pathlib_stat.py: Dimnsione : 589 Permessi : 0o100644 Proprietario : 1000 Dispositivo : 2053 Creato : Sat Aug 19 16:50:42 2017 Ultima modifica: Sat Aug 19 16:50:42 2017 Ultimo accesso : Sat Aug 19 16:50:42 2017 $ python3 pathlib_stat.py ../operator.xml ../operator.xml: Dimnsione : 3711 Permessi : 0o100664 Proprietario : 1000 Dispositivo : 2053 Creato : Wed Nov 2 22:43:09 2016 Ultima modifica: Thu Sep 29 18:48:13 2016 Ultimo accesso : Wed Aug 16 20:44:53 2017
Per un accesso semplificato a informazioni circa il proprietario di un file si usi owner()
e group()
.
# pathlib_ownership.py
import pathlib
p = pathlib.Path(__file__)
print('Il proprietario di {} è {}/{}'.format(p, p.owner(), p.group()))
Mentre stat()
ritorna valori di ID di sistema numerici, questi metodi cercano il nome associato all'identificativo.
$ python3 pathlib_ownership.py Il proprietario di pathlib_ownership.py è robby/robby
Il metodo touch()
funziona come il comando Unix touch per creare un file o aggiornare la data di modifica del file e i permessi.
# pathlib_touch.py
import pathlib
import time
p = pathlib.Path('toccato')
if p.exists():
print('esiste già')
else:
print('creato nuovo')
p.touch()
start = p.stat()
time.sleep(1)
p.touch()
end = p.stat()
print('Inizio:', time.ctime(start.st_mtime))
print('Fine :', time.ctime(end.st_mtime))
L'esecuzione di più di una volta di questo esempio aggiornerà il file esistente dopo la prima esecuzione.
$ python3 pathlib_touch.py creato nuovo Inizio: Sat Aug 19 16:59:08 2017 Fine : Sat Aug 19 16:59:09 2017 $ python3 pathlib_touch.py esiste già Inizio: Sat Aug 19 16:59:43 2017 Fine : Sat Aug 19 16:59:44 2017
Permessi
Sui sistemi tipo Unix, i permessi dei file possono essere cambiati usando chmod()
, passando il modo come intero. I valori possono essere costruiti usando le costanti definite nel modulo stat. Questo esempio agisce sul bit del permesso di esecuzione per l'utente alternandone il valore.
# pathlib_chmod.py
import os
import pathlib
import stat
# Crea un nuovo file di testo
f = pathlib.Path('pathlib_chmod_esempio.txt')
if f.exists():
f.unlink()
f.write_text('contenuto')
# Determa che permessi sono già impostati usando stat.
existing_permissions = stat.S_IMODE(f.stat().st_mode)
print('Prima: {:o}'.format(existing_permissions))
# Decide in che modo alternarli.
if not (existing_permissions & os.X_OK):
print('Aggiunto permesso di esecuzione')
new_permissions = existing_permissions | stat.S_IXUSR
else:
print('Rimosso permesso di esecuzione')
# usa xor per rimuovere il permesso di esecuzione utente
new_permissions = existing_permissions ^ stat.S_IXUSR
# Esegue la modifica e mostra il nuovo valore.
f.chmod(new_permissions)
after_permissions = stat.S_IMODE(f.stat().st_mode)
print('Dopo: {:o}'.format(after_permissions))
Questo script assume che sia abbiano i privilegi necessari per modificare il modo del file quando viene eseguito.
$ python3 pathlib_chmod.py Prima: 644 Aggiunto permesso di esecuzione Dopo: 744
Eliminare
Ci sono due metodi per rimuovere elementi dal filesystem, in base al tipo. Per eliminare una directory vuota si usi rmdir()
.
# pathlib_rmdir.py
import pathlib
p = pathlib.Path('dir_esempio')
print('Rimozione di {}'.format(p))
p.rmdir()
Viene sollevata una eccezione FileNotFoundError
se le condizioni di arrivo sono già presenti e la directory non esiste. Si avrà un errore anche se si tenta di rimuovere una directory non vuota.
$ python3 pathlib_rmdir.py Rimozione di dir_esempio $ python3 pathlib_rmdir.py Rimozione di dir_esempio Traceback (most recent call last): File "pathlib_rmdir.py", line 8, in ≶module> p.rmdir() File "/usr/lib/python3.5/pathlib.py", line 1262, in rmdir self._accessor.rmdir(self) File "/usr/lib/python3.5/pathlib.py", line 371, in wrapped return strfunc(str(pathobj), *args) FileNotFoundError: [Errno 2] No such file or directory: 'dir_esempio'
Per file, collegamenti simbolici e la maggior parte degli altri tipi di percorso si usi unlink()
.
# pathlib_unlink.py
import pathlib
p = pathlib.Path('toccato')
p.touch()
print('esiste prima della rimozione:', p.exists())
p.unlink()
print('esiste dopo la rimozione:', p.exists())
L'utente deve avere i privilegi per rimuovere il file, il collegamento simbolico, il socket o altro oggetto di filesystem.
$ python3 pathlib_unlink.py esiste prima della rimozione: True esiste dopo la rimozione: False
Vedere anche:
- pathlib
- La documentazione della libreria standard per questo modulo
- os.path
- Manipolazione di nomi di file indipendentemente dalla piattaforma
- Gestire i Permessi del File System
- Discussione su
os.stat()
edos.lstat()
- glob
- Verifica di corrispondenza su modello per nomi di file in Unix
- PEP 428
- Il modulo pathlib.