pkgutil - Utilità per Pacchetti
Scopo: Aggiunge al percorso di ricerca dei moduli quello di uno specifico pacchetto e lavora con risorse incluse in un pacchetto.
Il modulo pkgutil include funzioni per modificare le regole di importazione dei pacchetti Python e per caricare risorse diverse dal codice da file distribuiti all'interno di un pacchetto.
Percorsi di Importazione Pacchetti
La funzione extend_path()
è usata per modificare il percorso di ricerca e modificare il modo nel quale i sotto moduli sono importati dall'interno di un pacchetto in modo che diverse directory differenti possano essere combinate come se fossero una sola. Questa caratteristica può essere utilizzata per sovrascrivere versioni di pacchetti installati con versioni di sviluppo, oppure per combinare moduli specifici per la piattaforma e moduli condivisi in un singolo spazio dei nomi per un pacchetto.
Il modo più comune per chiamare extend_path()
è aggiungendo due righe al file __init__.py
all'interno del pacchetto.
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
extend_path()
scorre sys.path
alla ricerca di directory che includano una certa sottodirectory per il pacchetto fornita come secondo argomento. L'elenco di directory viene combinato con il valore del percorso passato come primo argomento e viene ritornato come una singola lista, adatta all'uso come percorso di importazione del pacchetto.
Un pacchetto di esempio chiamato demopkg
include due file, __init__.py
e shared.py
. Il file __init__.py
in demopkg1
contiene istruzioni print
per mostrare il percorso di ricerca prima e dopo che è stato modificato, per evidenziarne la differenza.
# demopkg1/__init__.py
import pkgutil
import pprint
print('demopkg1.__path__ before:')
pprint.pprint(__path__)
print()
__path__ = pkgutil.extend_path(__path__, __name__)
print('demopkg1.__path__ after:')
pprint.pprint(__path__)
print()
La directory extension
, con caratteristiche aggiunte per demopkg
, contiene altri tre file sorgente. C'è un file __init__.py
a ogni livello di directory e un not_shared.py
.
$ find extension -name '*.py' extension/demopkg1/__init__.py extension/demopkg1/not_shared.py extension/__init__.py
Questo semplice programma di test importa il pacchetto demopkg1
.
# pkgutil_extend_path.py
import demopkg1
print('demopkg1 :', demopkg1.__file__)
try:
import demopkg1.shared
except Exception as err:
print('demopkg1.shared : Non trovato ({})'.format(err))
else:
print('demopkg1.shared :', demopkg1.shared.__file__)
try:
import demopkg1.not_shared
except Exception as err:
print('demopkg1.not_shared: Non trovato ({})'.format(err))
else:
print('demopkg1.not_shared:', demopkg1.not_shared.__file__)
Quando il programma di test viene eseguito direttamente da riga di comando, il modulo not_shared.py
non viene trovato.
$ python3 pkgutil_extend_path.py demopkg1.__path__ before: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1'] demopkg1.__path__ after: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1'] demopkg1 : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/__init__.py demopkg1.shared : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/shared.py demopkg1.not_shared: Non trovato (No module named 'demopkg1.not_shared')
Tuttavia, se la directory extension
viene aggiunta a PYTHONPATH
e il programma viene rieseguito, si hanno risultati differenti.
$ PYTHONPATH=extension python3 pkgutil_extend_path.py demopkg1.__path__ before: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1'] demopkg1.__path__ after: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1', '/dati/dev/python/pymotw3restyling/dumpscripts/extension/demopkg1'] demopkg1 : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/__init__.py demopkg1.shared : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/shared.py demopkg1.not_shared: /dati/dev/python/pymotw3restyling/dumpscripts/extension/demopkg1/not_shared.py
La versione di demopkg1
all'interno della directory extension
è stata aggiunta al percorso di ricerca, quindi not_shared.py
viene trovato.
L'estensione del percorso è in questo modo utile per combinare versioni di pacchetti specifiche per la piattaforma con pacchetti comuni, specialmente se le versioni specifiche per la piattaforma includono moduli di estensione C.
Versioni di Sviluppo di Pacchetti
Nella fase di sviluppo di migliorie per un progetto, è normale la necessità di dover verificare modifiche a un pacchetto installato. Sostituire la copia installata con una versione di sviluppo potrebbe essere una cattiva idea, visto che potrebbe non essere corretta e altri strumenti nel sistema potrebbero dipendere dal pacchetto installato.
Una copia completamente separata del pacchetto potrebbe essere configurata in un ambiente di sviluppo usando virtualenv
oppure venv, ma per piccole modifiche potrebbe essere eccessivo l'impegno di impostare un ambiente virtuale con tutte le dipendenze.
Un'altra opzione è usare pkgutil
per modificare il percorso di ricerca dei moduli per i moduli che appartengono al pacchetto in sviluppo. In questo caso, tuttavia, il percorso deve essere invertito in modo che il percorso della versione di sviluppo venga esaminato prima di quello della versione installata.
In questo caso un pacchetto demopkg2
contiene un file __init__.py
e overloaded.py
, con la funzione in fase di sviluppo collocata in demopkg2/overloaded.py
.
# develop/demopkg2/overloaded.py
def func():
print("Questa e' la versione installata di func().")
e demopkg2/__init__.py
contiene:
# demopkg2/__init__.py
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
__path__.reverse()
reverse()
viene utilizzato per assicurarsi che qualunque directory aggiunta al percorso di ricerca da pkgutil
venga esaminata per trovare importazioni relative al percorso di sviluppo prima della importazioni della versione predefinita.
Il programma importa demopkg2.overloaded
e chiama func()
.
# pkgutil_devel.py
import demopkg2
print('demopkg2 :', demopkg2.__file__)
import demopkg2.overloaded
print('demopkg2.overloaded:', demopkg2.overloaded.__file__)
print()
demopkg2.overloaded.func()
Eseguendolo senza particolari trattamenti del percorso produce il risultato dalla versione installata di func()
.
$ python3 pkgutil_devel.py demopkg2 : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg2/__init__.py demopkg2.overloaded: /dati/dev/python/pymotw3restyling/dumpscripts/demopkg2/overloaded.py Questa e' la versione installata di func().
La directory di sviluppo contiene:
$ find develop/demopkg2 -name '*.py' develop/demopkg2/__init__.py develop/demopkg2/overloaded.py
All'interno di overloaded.py
si trova la funzione modificata.
# develop/demopkg2/overloaded.py
def func():
print("Questa e' la versione di sviluppo di func().")
che verrà caricata quando il programma di test viene eseguito con la directory develop
nel percorso di ricerca.
$ PYTHONPATH=develop python3 pkgutil_devel.py demopkg2 : /dati/dev/python/pymotw3restyling/dumpscripts/demopkg2/__init__.py demopkg2.overloaded: /dati/dev/python/pymotw3restyling/dumpscripts/develop/demopkg2/overloaded.py Questa e' la versione di sviluppo di func().
Gestire Percorsi con File PKG
Il primo esempio illustrava come estendere il percorso di ricerca usando directory extra incluse in PYTHONPATH
. E' anche possibile aggiungere elementi al percorso di ricerca usando dei file *.pkg
che contengono nomi di directory. I file PKG sono simili ai file PTH usati dal modulo site. Possono contenere nomi di directory, uno per riga, da aggiungere al percorso di ricerca per il pacchetto.
Un altro modo per strutturare porzioni specfiche per la piattaforma dell'applicazione dal primo esempio è usare una directory separata per ogni sistema operativo, e includere un file .pkg
per estendere il percorso di ricerca.
Questo esempio usa gli stessi file in demopkg1
, e include anche i seguenti file:
$ find so_* -type f so_due/demopkg1/__init__.py so_due/demopkg1/not_shared.py so_due/demopkg1/__pycache__/not_shared.cpython-36.pyc so_due/demopkg1/__pycache__/not_shared.cpython-38.pyc so_due/demopkg1.pkg so_uno/demopkg1/__init__.py so_uno/demopkg1/not_shared.py so_uno/demopkg1/__pycache__/not_shared.cpython-36.pyc so_uno/demopkg1/__pycache__/not_shared.cpython-38.pyc so_uno/demopkg1.pkg
I file PKG sono chiamati demopkg1.pkg
per farli corrispondere con il pacchetto che viene esteso. Entrambi contengono questa riga:
demopkg
Questo programma mostra le versioni dei moduli importati.
# pkgutil_os_specific.py
import demopkg1
print('demopkg1:', demopkg1.__file__)
import demopkg1.shared
print('demopkg1.shared:', demopkg1.shared.__file__)
import demopkg1.not_shared
print('demopkg1.not_shared:', demopkg1.not_shared.__file__)
Un semplice script può essere usato per alternare i due pacchetti.
# with_os.sh
#!/bin/sh
export PYTHONPATH=so_${1}
echo "PYTHONPATH=$PYTHONPATH"
echo
python3 pkgutil_os_specific.py
Quando viene eseguito con uno
o due
come argomento, il percorso viene modificato.
$ ./with_os.sh uno PYTHONPATH=so_uno demopkg1.__path__ before: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1'] demopkg1.__path__ after: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1', '/dati/dev/python/pymotw3restyling/dumpscripts/so_uno/demopkg1', 'demopkg'] demopkg1: /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/__init__.py demopkg1.shared: /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/shared.py demopkg1.not_shared: /dati/dev/python/pymotw3restyling/dumpscripts/so_uno/demopkg1/not_shared.py
$ ./with_os.sh due PYTHONPATH=so_due demopkg1.__path__ before: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1'] demopkg1.__path__ after: ['/dati/dev/python/pymotw3restyling/dumpscripts/demopkg1', '/dati/dev/python/pymotw3restyling/dumpscripts/so_due/demopkg1', 'demopkg'] demopkg1: /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/__init__.py demopkg1.shared: /dati/dev/python/pymotw3restyling/dumpscripts/demopkg1/shared.py demopkg1.not_shared: /dati/dev/python/pymotw3restyling/dumpscripts/so_due/demopkg1/not_shared.py
I file PKG possono apparire in qualunque parte del normale percorso di ricerca, quindi anche un singolo file PKG nella directory di lavoro corrente potrebbe essere usato per includere un ramo di sviluppo.
Pacchetti Annidati
Per pacchetti annidati, è solamente necessario modificare il percorso del pacchetto di livello superiore. Ad esempio, con questa struttura di directory.
$ find nested -name '*.py' nested/__init__.py nested/second/deep.py nested/second/__init__.py nested/shallow.py
Dove nested/__init__.py
contiene.
# nested/__Init__.py
import pkgutil
__path__ = pkgutil.extend_path(__path__, __name__)
__path__.reverse()
ed una branca di sviluppo tipo.
$ find develop/nested -name '*.py' develop/nested/__init__.py develop/nested/second/deep.py develop/nested/second/__init__.py develop/nested/shallow.py
Entrambi i moduli shallow
e deep
contengono una semplice funzione che stampa un messaggio che indica se provengono o meno dalla versione installata oppure quella di sviluppo.
Questo programma di test si avvale dei nuovi pacchetti.
# pkgutil_nested.py
import nested
import nested.shallow
print('nested.shallow:', nested.shallow.__file__)
nested.shallow.func()
print()
import nested.second.deep
print('nested.second.deep:', nested.second.deep.__file__)
nested.second.deep.func()
Quando viene eseguito pkgutil_nested.py
senza alcuna manipolazione del percorso, vengono usate le versioni installate di entrambi i moduli.
$ python3 pkgutil_nested.py nested.shallow: /dati/dev/python/pymotw3restyling/dumpscripts/nested/shallow.py Questa func() proviene dalla versione installata di nested.shallow nested.second.deep: /dati/dev/python/pymotw3restyling/dumpscripts/nested/second/deep.py Questa func() proviene dalla versione installata di nested.second.deep
Quando viene aggiunta la directory develop
al percorso, le versioni di sviluppo di entrambi i metodi vengono eseguite in luogo di quelle installate.
$ PYTHONPATH=develop python3 pkgutil_nested.py nested.shallow: /dati/dev/python/pymotw3restyling/dumpscripts/develop/nested/shallow.py Questa func() proviene dalla versione di sviluppo di nested.shallow nested.second.deep: /dati/dev/python/pymotw3restyling/dumpscripts/develop/nested/second/deep.py Questa func() proviene dalla versione di sviluppo di nested.second.deep
Pacchetti con Dati
Oltre al codice, i pacchetti Python possono contenere file dati tipo template, file di configurazione predefiniti, immagini e altri file di supporto usati dal codice nel pacchetto. La funzione get_date()
fornisce accesso ai dati nei file in modo agnostico, quindi non importa se il pacchetto viene distribuito come EGG, parte di un binario fissato, o normali file nel sistema.
Con un pacchetto pkgwithdata
che contiene una directory templates
.
$ find pkgwithdata -type f pkgwithdata/templates/fromzip.html pkgwithdata/templates/base.html pkgwithdata/__init__.py pkgwithdata/__pycache__/__init__.cpython-36.pyc
Il file pkgwithdata/templates/base.html
contiene un semplice template HTML.
<!-- # pkgwithdata/templates/base.html -->
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<html> <head>
<title>PyMOTW Template</title>
</head>
<body>
<h1>Template di esempio</h1>
<p>Questo e' un file di dati di esempio..</p>
</body>
</html>
Il programma usa get_data()
per ottenere il contenuto del template e lo stampa.
# pkgutil_get_data.py
import pkgutil
template = pkgutil.get_data('pkgwithdata', 'templates/base.html')
print(template.decode('utf-8'))
Gli argomenti di get_data()
sono il nome del pacchetto e un nome di file relativo al livello superiore del pacchetto. Il valore di ritorno è una sequenza di byte, quindi deve essere decodificato in UTF-8 prima della stampa.
$ python3 pkgutil_get_data.py <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> <title>PyMOTW Template</title> </head> <body> <h1>Template di esempio</h1> <p>Questo e' un file di dati di esempio..</p> </body> </html>
get_data()
è in formato agnostico rispetto alla distribuzione in quanto usa gli agganci di importazione definiti per accedere ai contenuti di un pacchetto definiti in PEP 302. E' possibile usare un qualunque caricatore che fornisca gli agganci, compreso l'importatore di archivi ZIP in zipfile.
# pkgutil_get_data_zip.py
import pkgutil
import zipfile
import sys
# Crea un file ZIP con il codice dalla directory corrente
# ed il template usando un nome che non appare nel filesystem.locale
with zipfile.PyZipFile('pkgwithdatainzip.zip', mode='w') as zf:
zf.writepy('pkgwithdata/.')
zf.write('pkgwithdata/templates/base.html',
'pkgwithdata/templates/fromzip.html',
)
# Aggiunge il file ZIP al percorso di importazione.
sys.path.insert(0, 'pkgwithdatainzip.zip')
# Importa pkgwithdata per mostrare che proviene dall'archivio ZIP.
import pkgwithdata
print('Loading pkgwithdata from', pkgwithdata.__file__)
# Stampa il contenuto del template.
print('\nTemplate:')
data = pkgutil.get_data('pkgwithdata', 'templates/fromzip.html')
print(data.decode('utf-8'))
Questo esempio usa PyZipFile.writepy()
per creare un archivio ZIP che contiene una copia del pacchetto pkgwithdata
, inclusa una versione rinominata del file template. Quindi aggiunge l'archivio ZIP al percorso di importazione prima di usare pkgutil
per caricare il template e stamparlo. Si faccia riferimento a zipfile per maggiori dettagli sull'uso di writepy()
.
$ python3 pkgutil_get_data_zip.py $ python3 pkgutil_get_data_zip.py Loading pkgwithdata from .../pkgwithdata/__init__.py Template: <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <html> <head> <title>PyMOTW Template</title> </head> <body> <h1>Template di esempio</h1> <p>Questo e' un file di dati di esempio..</p> </body> </html>
Vedere anche:
- pkgutil
- La documentazione della libreria standard per questo modulo.
- virtualenv
- Lo script di ambiente virtuale di Ian Bicking
- distutils
- Strumenti per gestione pacchetti dalla libreria standard di Python
- setuptools
- Strumenti di gestione pacchetti della prossima generazione.
- PEP 302
- Agganci di importazione.
- zipfile
- Crea archivi ZIP importabili
- zipimport
- Importatore di pacchetti in archivi ZIP.