Un prélude packaging:
Avant même de pouvoir vous soucier de la lecture des fichiers de ressources, la première étape consiste à vous assurer que les fichiers de données sont emballés dans votre distribution en premier lieu - il est facile de les lire directement à partir de l'arborescence source, mais la partie importante est de faire assurez-vous que ces fichiers de ressources sont accessibles à partir du code dans un package installé .
Structurez votre projet comme celui - ci, en plaçant les fichiers de données dans un sous - répertoire à l' intérieur du paquet:
.
├── package
│ ├── __init__.py
│ ├── templates
│ │ └── temp_file
│ ├── mymodule1.py
│ └── mymodule2.py
├── README.rst
├── MANIFEST.in
└── setup.py
Vous devriez passer include_package_data=True
l' setup()
appel. Le fichier manifeste n'est nécessaire que si vous souhaitez utiliser setuptools / distutils et créer des distributions source. Pour vous assurer que le templates/temp_file
fichier est empaqueté pour cet exemple de structure de projet, ajoutez une ligne comme celle-ci dans le fichier manifeste:
recursive-include package *
Note cruelle historique: l' utilisation d'un fichier manifeste n'est pas nécessaire pour les backends de construction modernes tels que flit, poetry, qui incluront les fichiers de données du package par défaut. Donc, si vous utilisez pyproject.toml
et que vous n'avez pas de setup.py
fichier, vous pouvez ignorer tout ce qui concerne MANIFEST.in
.
Maintenant, avec l'emballage à l'écart, sur la partie lecture ...
Recommandation:
Utilisez les pkgutil
API de bibliothèque standard . Cela va ressembler à ceci dans le code de la bibliothèque:
# 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))
Cela fonctionne en zips. Il fonctionne sur Python 2 et Python 3. Il ne nécessite pas de dépendances tierces. Je ne suis pas vraiment au courant des inconvénients (si vous l'êtes, veuillez commenter la réponse).
Mauvaises façons d'éviter:
Mauvaise façon n ° 1: utiliser des chemins relatifs à partir d'un fichier source
C'est actuellement la réponse acceptée. Au mieux, cela ressemble à ceci:
from pathlib import Path
resource_path = Path(__file__).parent / "templates"
data = resource_path.joinpath("temp_file").read_bytes()
print("data", repr(data))
Qu'est-ce qui ne va pas avec ça? L'hypothèse selon laquelle vous avez des fichiers et des sous-répertoires disponibles n'est pas correcte. Cette approche ne fonctionne pas si vous exécutez du code qui est emballé dans un zip ou une roue, et il peut être entièrement hors du contrôle de l'utilisateur que votre paquet soit extrait ou non dans un système de fichiers.
Mauvaise façon n ° 2: utiliser les API pkg_resources
Ceci est décrit dans la réponse la plus votée. Cela ressemble à quelque chose comme ceci:
from pkg_resources import resource_string
data = resource_string(__name__, "templates/temp_file")
print("data", repr(data))
Qu'est-ce qui ne va pas avec ça? Il ajoute une dépendance d' exécution sur setuptools , qui devrait de préférence être une dépendance de temps d' installation uniquement. L'importation et l'utilisation pkg_resources
peuvent devenir très lentes, car le code crée un ensemble de travail de tous les packages installés, même si vous n'étiez intéressé que par vos propres ressources de package. Ce n'est pas un gros problème au moment de l'installation (puisque l'installation est unique), mais c'est moche au moment de l'exécution.
Mauvaise façon n ° 3: utiliser les API importlib.resources
C'est actuellement la recommandation dans la réponse la plus votée. C'est un ajout récent de bibliothèque standard ( nouveau dans Python 3.7 ), mais il existe également un backport disponible. Cela ressemble à ceci:
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))
Qu'est-ce qui ne va pas avec ça? Eh bien, malheureusement, cela ne fonctionne pas ... encore. Il s'agit toujours d'une API incomplète, l'utilisation importlib.resources
vous obligera à ajouter un fichier vide templates/__init__.py
afin que les fichiers de données résident dans un sous-package plutôt que dans un sous-répertoire. Il exposera également le package/templates
sous - répertoire en tant que package.templates
sous-package importable à part entière. Si ce n'est pas un gros problème et que cela ne vous dérange pas, vous pouvez alors y ajouter le __init__.py
fichier et utiliser le système d'importation pour accéder aux ressources. Cependant, pendant que vous y êtes, vous pouvez aussi bien en faire un my_resources.py
fichier à la place, et définir simplement des octets ou des variables de chaîne dans le module, puis les importer dans du code Python. C'est le système d'importation qui fait le gros du travail ici de toute façon.
Exemple de projet:
J'ai créé un exemple de projet sur github et téléchargé sur PyPI , qui illustre les quatre approches décrites ci-dessus. Essayez-le avec:
$ pip install resources-example
$ resources-example
Voir https://github.com/wimglenn/resources-example pour plus d'informations.