Pourriez-vous me dire comment puis-je lire un fichier qui se trouve dans mon package Python?
Ma situation
Un package que je charge a un certain nombre de modèles (fichiers texte utilisés comme chaînes) que je souhaite charger à partir du programme. Mais comment spécifier le chemin vers un tel fichier?
Imaginez que je souhaite lire un fichier à partir de:
package\templates\temp_file
Une sorte de manipulation de chemin? Suivi du chemin de base du package?
Réponses:
[ajouté 15/06/2016: apparemment, cela ne fonctionne pas dans toutes les situations. veuillez vous référer aux autres réponses]
la source
TLDR; Utilisez le
importlib.resources
module standard-library comme expliqué dans la méthode n ° 2 ci-dessous.Le traditionnel
pkg_resources
fromsetuptools
n'est plus recommandé car la nouvelle méthode:setuptools
), mais comptez uniquement sur la bibliothèque standard de Python.J'ai gardé la liste traditionnelle en premier, pour expliquer les différences avec la nouvelle méthode lors du portage du code existant (le portage est également expliqué ici ).
Supposons que vos modèles se trouvent dans un dossier imbriqué dans le package de votre module:
1) Utilisation
pkg_resources
desetuptools
(lent)Vous pouvez utiliser le
pkg_resources
package de la distribution setuptools , mais cela a un coût en termes de performances :... et notez que selon les Setuptools /
pkg_resources
docs, vous ne devez pas utiliseros.path.join
:2) Python> = 3.7, ou en utilisant la
importlib_resources
bibliothèque rétroportéeUtilisez le
importlib.resources
module de la bibliothèque standard qui est plus efficace quesetuptools
ci-dessus:Pour l'exemple posé dans la question, il faut maintenant:
<your_package>/templates/
en un package approprié, en créant un__init__.py
fichier vide dedansimport
instruction simple (éventuellement relative) (plus d'analyse des noms de package / module),resource_name = "temp_file"
(pas de chemin).la source
NotImplementedError: Can't perform this operation for loaders without 'get_data()'
des idées?importlib.resources
et nepkg_resources
sont pas nécessairement compatibles .importlib.resources
fonctionne avec les fichiers zip ajoutés àsys.path
, setuptools etpkg_resources
fonctionne avec les fichiers egg, qui sont des fichiers zip stockés dans un répertoire auquel il est lui-même ajoutésys.path
. Par exemple, avecsys.path = [..., '.../foo', '.../bar.zip']
, les œufs entrent.../foo
, mais les emballagesbar.zip
peuvent également être importés. Vous ne pouvez pas utiliserpkg_resources
pour extraire des données de packages dansbar.zip
. Je n'ai pas vérifié si setuptools enregistre le chargeur nécessaire pourimportlib.resources
travailler avec des œufs.Package has no location
apparaît?templates
dans l'exemple), vous pouvez définir l'package
argument sur__package__
, par exemplepkg_resources.read_text(__package__, 'temp_file')
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:
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 letemplates/temp_file
fichier est empaqueté pour cet exemple de structure de projet, ajoutez une ligne comme celle-ci dans le fichier manifeste: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 desetup.py
fichier, vous pouvez ignorer tout ce qui concerneMANIFEST.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: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:
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:
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:
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 videtemplates/__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 lepackage/templates
sous - répertoire en tant quepackage.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 unmy_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:
Voir https://github.com/wimglenn/resources-example pour plus d'informations.
la source
importlib.resources
malgré toutes ces lacunes une API incomplète qui est déjà en attente de dépréciation ? Plus récent n'est pas nécessairement meilleur. Dites-moi quels avantages offre-t-il réellement par rapport à stdlib pkgutil, dont votre réponse ne fait aucune mention?pkgutil.get_data()
confirmé mon instinct - c'est une API sous-développée et à déconseiller. Cela dit, je suis d'accord avec vous, ceimportlib.resources
n'est pas une bien meilleure alternative, mais jusqu'à ce que PY3.10 résout ce problème, je maintiens ce choix, il a appris qu'il ne s'agit pas simplement d'un autre "standard" recommandé par la documentation.pkgutil
n'est pas du tout mentionné dans le calendrier de dépréciation de PEP 594 - Retrait des piles épuisées de la bibliothèque standard , et il est peu probable qu'il soit supprimé sans une bonne raison. Il existe depuis Python 2.3 et est spécifié dans le cadre du protocole de chargement dans PEP 302 . Utiliser une "API sous-définie" n'est pas une réponse très convaincante, qui pourrait décrire la majorité de la bibliothèque standard Python!pkgutil
dans presque tous les sens. Votre "instinct" et votre appel à l'autorité n'ont aucun sens pour moi, s'il y a des problèmes avec lesget_data
chargeurs, montrez-leur des preuves et des exemples pratiques.Dans le cas où vous avez cette structure
vous avez besoin de ce code:
L'étrange partie "toujours utiliser la barre oblique" provient des
setuptools
APIAu cas où vous vous demandez où se trouve la documentation:
la source
pkg_resources
a des frais généraux quipkgutil
surmonte. De plus, si le code fourni est exécuté comme point d'entrée,__name__
il évaluera à__main__
, pas le nom du package.Le contenu de "10.8. Reading Datafiles Within a Package" de Python Cookbook, Third Edition par David Beazley et Brian K. Jones donnant les réponses.
Je vais juste l'amener ici:
Supposons que vous ayez un package avec des fichiers organisés comme suit:
Supposons maintenant que le fichier spam.py veuille lire le contenu du fichier somedata.dat. Pour ce faire, utilisez le code suivant:
Les données variables résultantes seront une chaîne d'octets contenant le contenu brut du fichier.
Le premier argument de get_data () est une chaîne contenant le nom du package. Vous pouvez le fournir directement ou utiliser une variable spéciale, telle que
__package__
. Le deuxième argument est le nom relatif du fichier dans le package. Si nécessaire, vous pouvez naviguer dans différents répertoires en utilisant les conventions de nom de fichier Unix standard tant que le répertoire final se trouve toujours dans le package.De cette façon, le package peut être installé en tant que répertoire, .zip ou .egg.
la source
La réponse acceptée devrait être d'utiliser
importlib.resources
.pkgutil.get_data
exige également que l'argumentpackage
soit un package non-espace de noms ( voir la documentation pkgutil ). Par conséquent, le répertoire contenant la ressource doit avoir un__init__.py
fichier, ce qui lui confère exactement les mêmes limitations queimportlib.resources
. Si le problème des frais générauxpkg_resources
n'est pas un problème, c'est également une alternative acceptable.la source
Chaque module python de votre package a un
__file__
attributVous pouvez l'utiliser comme:
Pour les ressources sur les œufs, voir: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources
la source
en supposant que vous utilisez un fichier œuf; non extrait:
J'ai "résolu" ceci dans un projet récent, en utilisant un script de post-installation, qui extrait mes modèles de l'oeuf (fichier zip) dans le répertoire approprié du système de fichiers. C'était la solution la plus rapide et la plus fiable que j'ai trouvée, car travailler avec
__path__[0]
peut parfois mal tourner (je ne me souviens pas du nom, mais je suis tombé sur au moins une bibliothèque, cela a ajouté quelque chose devant cette liste!).De plus, les fichiers d'œufs sont généralement extraits à la volée vers un emplacement temporaire appelé «cache d'œufs». Vous pouvez changer cet emplacement en utilisant une variable d'environnement, soit avant de démarrer votre script, soit même plus tard, par exemple.
Cependant, il existe pkg_resources qui pourrait faire le travail correctement.
la source