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=Truela setup()chiamata. Il file manifest è necessario solo se si desidera utilizzare setuptools / distutils e creare distribuzioni di sorgenti. Per assicurarti che templates/temp_filevenga 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.tomle non hai un setup.pyfile, puoi ignorare tutto ciò che riguarda MANIFEST.in.
Ora, con l'imballaggio fuori mano, sulla parte di lettura ...
Raccomandazione:
Usa le pkgutilAPI 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_resourcespossono 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.resourcesrichiederà di aggiungere un file vuoto templates/__init__.pyin modo che i file di dati risiedano all'interno di un sottopacchetto piuttosto che in una sottodirectory. Inoltre esporrà la package/templatessottodirectory come un sottopacchetto importabile package.templatesa sé stante. Se questo non è un grosso problema e non ti dà fastidio, puoi andare avanti e aggiungere il __init__.pyfile lì e utilizzare il sistema di importazione per accedere alle risorse. Tuttavia, già che ci sei, potresti anche trasformarlo in un my_resources.pyfile 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.