Un preludio al packaging:
Prima ancora che tu possa preoccuparti di leggere i file di risorse, il primo passo è assicurarti che i file di dati vengano inseriti nella tua distribuzione in primo luogo: è facile leggerli direttamente dall'albero dei sorgenti, ma la parte importante è fare assicurarsi che questi file di risorse siano accessibili dal codice all'interno di un pacchetto installato .
Struttura il tuo progetto in questo modo, inserendo i file di dati in una sottodirectory all'interno del pacchetto:
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
Dovresti passare include_package_data=True
la setup()
chiamata. Il file manifest è necessario solo se si desidera utilizzare setuptools / distutils e creare distribuzioni di sorgenti. Per assicurarti che templates/temp_file
venga impacchettato per questa struttura di progetto di esempio, aggiungi una riga come questa nel file manifest:
recursive-include package *
Nota storica: l' utilizzo di un file manifest non è necessario per i backend di build moderni come flit, poetry, che includeranno i file di dati del pacchetto per impostazione predefinita. Quindi, se stai usando pyproject.toml
e non hai un setup.py
file, puoi ignorare tutto ciò che riguarda MANIFEST.in
.
Ora, con l'imballaggio fuori mano, sulla parte di lettura ...
Raccomandazione:
Usa le pkgutil
API della libreria standard . Apparirà così nel codice della libreria:
# within package/mymodule1.py, for example
import pkgutil
data = pkgutil.get_data(__name__, "templates/temp_file")
print("data:", repr(data))
text = pkgutil.get_data(__name__, "templates/temp_file").decode()
print("text:", repr(text))
Funziona con le zip. Funziona su Python 2 e Python 3. Non richiede dipendenze di terze parti. Non sono realmente a conoscenza di eventuali svantaggi (se lo sei, allora per favore commenta la risposta).
Cattivi modi per evitare:
Modo sbagliato n. 1: utilizzo di percorsi relativi da un file sorgente
Questa è attualmente la risposta accettata. Nella migliore delle ipotesi, assomiglia a questo:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
Cosa c'è che non va? Il presupposto che siano disponibili file e sottodirectory non è corretto. Questo approccio non funziona se si esegue codice che è impacchettato in uno zip o in una ruota, e potrebbe essere completamente fuori dal controllo dell'utente se il pacchetto viene estratto o meno in un filesystem.
Modo sbagliato n. 2: utilizzo delle API pkg_resources
Questo è descritto nella risposta più votata. Assomiglia a questo:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
Cosa c'è che non va? Aggiunge una dipendenza di runtime da setuptools , che dovrebbe essere preferibilmente solo una dipendenza dal tempo di installazione . L'importazione e l'utilizzo pkg_resources
possono diventare molto lenti, poiché il codice costruisce un set funzionante di tutti i pacchetti installati, anche se eri interessato solo alle risorse del tuo pacchetto. Non è un grosso problema al momento dell'installazione (poiché l'installazione è una tantum), ma è brutto in fase di esecuzione.
Modo sbagliato # 3: utilizzo delle API importlib.resources
Questa è attualmente la raccomandazione nella risposta più votata. È una recente aggiunta alla libreria standard ( nuova in Python 3.7 ), ma è disponibile anche un backport. Assomiglia a questo:
try:
from importlib.resources import read_binary
from importlib.resources import read_text
except ImportError:
# Python 2.x backport
from importlib_resources import read_binary
from importlib_resources import read_text
data = read_binary("package.templates", "temp_file")
print("data", repr(data))
text = read_text("package.templates", "temp_file")
print("text", repr(text))
Cosa c'è che non va? Ebbene, sfortunatamente, non funziona ... ancora. Questa è ancora un'API incompleta, l'utilizzo importlib.resources
richiederà di aggiungere un file vuoto templates/__init__.py
in modo che i file di dati risiedano all'interno di un sottopacchetto piuttosto che in una sottodirectory. Inoltre esporrà la package/templates
sottodirectory come un sottopacchetto importabile package.templates
a sé stante. Se questo non è un grosso problema e non ti dà fastidio, puoi andare avanti e aggiungere il __init__.py
file lì e utilizzare il sistema di importazione per accedere alle risorse. Tuttavia, già che ci sei, potresti anche trasformarlo in un my_resources.py
file e definire solo alcuni byte o variabili stringa nel modulo, quindi importarli nel codice Python. In entrambi i casi è il sistema di importazione che fa il lavoro pesante.
Progetto di esempio:
Ho creato un progetto di esempio su GitHub e caricato su PyPI , che mostra tutti e quattro gli approcci discussi sopra. Provalo con:
$ pip install resources-example
$ resources-example
Vedi https://github.com/wimglenn/resources-example per maggiori informazioni.