Accéder aux données dans le sous-répertoire du package

130

J'écris un package python avec des modules qui doivent ouvrir des fichiers de données dans un ./data/sous - répertoire. À l'heure actuelle, j'ai les chemins d'accès aux fichiers codés en dur dans mes classes et mes fonctions. Je voudrais écrire un code plus robuste qui puisse accéder au sous-répertoire quel que soit l'endroit où il est installé sur le système de l'utilisateur.

J'ai essayé diverses méthodes, mais jusqu'à présent, je n'ai pas eu de chance. Il semble que la plupart des commandes "répertoire courant" renvoient le répertoire de l'interpréteur python du système, et non le répertoire du module.

Cela semble être un problème banal et courant. Pourtant, je n'arrive pas à comprendre. Une partie du problème est que mes fichiers de données ne sont pas des .pyfichiers, je ne peux donc pas utiliser les fonctions d'importation et autres.

Aucune suggestion?

À l'heure actuelle, mon répertoire de paquets ressemble à:

/
__init__.py
module1.py
module2.py
data/   
   data.txt

J'essaye d'accéder à data.txtpartir de module*.py!

Jacob Lyles
la source

Réponses:

24

Vous pouvez utiliser __file__pour obtenir le chemin d'accès au package, comme ceci:

import os
this_dir, this_filename = os.path.split(__file__)
DATA_PATH = os.path.join(this_dir, "data", "data.txt")
print open(DATA_PATH).read()
RichieHindle
la source
44
Cela ne fonctionnera pas si les fichiers sont dans une distribution (IE. Egg). Utilisez pkg_resources pour accéder au fichier de données.
Chris
2
En effet, c'est cassé.
Federico
1
De plus, __file__ne fonctionne pas avec py2exe, car la valeur sera le chemin du fichier zip.
Pod
1
Cela a fonctionné pour moi. N'a eu aucun problème. J'utilise python 3.6
Jorge
1
Cela ne fonctionnera pas en cas de distribution (œuf, etc.).
Adarsh ​​Trivedi
166

La manière standard de le faire est d'utiliser les packages setuptools et pkg_resources.

Vous pouvez mettre en page votre package selon la hiérarchie suivante et configurer le fichier de configuration du package pour qu'il pointe vos ressources de données, selon ce lien:

http://docs.python.org/distutils/setupscript.html#installing-package-data

Vous pouvez ensuite retrouver et utiliser ces fichiers en utilisant pkg_resources, selon ce lien:

http://peak.telecommunity.com/DevCenter/PkgResources#basic-resource-access

import pkg_resources

DATA_PATH = pkg_resources.resource_filename('<package name>', 'data/')
DB_FILE = pkg_resources.resource_filename('<package name>', 'data/sqlite.db')
elliot42
la source
7
Est-ce que pkg_resources ne créera pas une dépendance d' exécution sur setuptools ? Par exemple, je redistribue un paquet Debian alors pourquoi devrais-je dépendre python-setuptoolsuniquement pour cela? Jusqu'à présent, cela __file__fonctionne bien pour moi.
mlt
4
Pourquoi c'est mieux: la classe ResourceManager fournit un accès uniforme aux ressources du package, que ces ressources existent sous forme de fichiers et de répertoires ou qu'elles soient compressées dans une archive quelconque
vrdhn
4
Brillante suggestion, merci. J'ai implémenté un fichier standard ouvert en utilisantfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eageranalyst
5
Comment cela fonctionnera-t-il pour utiliser le package lorsqu'il n'est pas installé? Je veux juste tester localement
Claudiu
11
En python 3.7, importlib.resourcesremplace pkg_resourcesdans ce but (en raison de problèmes de performances).
benjimin
13

Fournir une solution qui fonctionne aujourd'hui. Utilisez définitivement cette API pour ne pas réinventer toutes ces roues.

Un vrai nom de fichier de système de fichiers est nécessaire. Les œufs zippés seront extraits dans un répertoire de cache:

from pkg_resources import resource_filename, Requirement

path_to_vik_logo = resource_filename(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Renvoie un objet de type fichier lisible pour la ressource spécifiée; il peut s'agir d'un fichier réel, d'un StringIO ou d'un objet similaire. Le flux est en «mode binaire», en ce sens que tous les octets contenus dans la ressource seront lus tels quels.

from pkg_resources import resource_stream, Requirement

vik_logo_as_stream = resource_stream(Requirement.parse("enb.portals"), "enb/portals/reports/VIK_logo.png")

Découverte de package et accès aux ressources à l'aide de pkg_resources

Sascha Gottfried
la source
10

Il est souvent inutile de faire une réponse qui détaille un code qui ne fonctionne pas tel quel , mais je pense que c'est une exception. Ajout de Python 3.7 importlib.resourcesqui est censé remplacer pkg_resources. Cela fonctionnerait pour accéder aux fichiers dans des packages qui n'ont pas de barres obliques dans leurs noms, c'est-à-dire

foo/
    __init__.py
    module1.py
    module2.py
    data/   
       data.txt
    data2.txt

c'est à dire que vous pouvez accéder à l' data2.txtintérieur du packagefoo avec par exemple

importlib.resources.open_binary('foo', 'data2.txt')

mais cela échouerait avec une exception pour

>>> importlib.resources.open_binary('foo', 'data/data.txt')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.7/importlib/resources.py", line 87, in open_binary
    resource = _normalize_path(resource)
  File "/usr/lib/python3.7/importlib/resources.py", line 61, in _normalize_path
    raise ValueError('{!r} must be only a file name'.format(path))
ValueError: 'data/data2.txt' must be only a file name

Cela ne peut pas être fixé , sauf en plaçant __init__.pydans data, puis l' utiliser comme un paquet:

importlib.resources.open_binary('foo.data', 'data.txt')

La raison de ce comportement est "c'est par conception" ; mais le design pourrait changer ...

Antti Haapala
la source
Avez-vous un meilleur lien pour "c'est par conception" qu'une vidéo YouTube - de préférence avec du texte?
gerrit
@gerrit le 2ème contient du texte. "This was a deliberate choice, but I think you have a valid use case. @brettcannon what do you think? And if we allow this, should we make sure it gets into Python 3.7?"
Antti Haapala
8

Vous avez besoin d'un nom pour tout votre module, l'arborescence de répertoires qui vous est donnée ne répertorie pas ce détail, pour moi cela a fonctionné:

import pkg_resources
print(    
    pkg_resources.resource_filename(__name__, 'data/data.txt')
)

Notamment, setuptools ne semble pas résoudre les fichiers en fonction d'une correspondance de nom avec des fichiers de données compressés, vous devez donc inclure le data/préfixe à peu près quoi qu'il arrive . Vous pouvez utiliser os.path.join('data', 'data.txt)si vous avez besoin de séparateurs de répertoires alternatifs. En général, je ne trouve aucun problème de compatibilité avec les séparateurs de répertoires de style Unix codés en dur.

ThorSummoner
la source
docs.python.org/3.6/distutils/... > Notez que tous les chemins (fichiers ou répertoires) fournis dans le script de configuration doivent être écrits en utilisant la convention Unix, c'est-à-dire séparés par des barres obliques . Les Distutils se chargeront de convertir cette représentation indépendante de la plate-forme en ce qui est approprié sur votre plate-forme actuelle avant d'utiliser réellement le chemin. Cela rend votre script d'installation portable sur tous les systèmes d'exploitation, ce qui est bien sûr l'un des principaux objectifs des Distutils. Dans cet esprit, tous les chemins d'accès de ce document sont séparés par des barres obliques.
changyuheng
6

Je pense que j'ai cherché une réponse.

Je crée un module data_path.py, que j'importe dans mes autres modules contenant:

data_path = os.path.join(os.path.dirname(__file__),'data')

Et puis j'ouvre tous mes fichiers avec

open(os.path.join(data_path,'filename'), <param>)
Jacob Lyles
la source
2
Cela ne fonctionnera pas lorsque la ressource se trouve dans une distribution d'archive (telle qu'un œuf zippé). Préférez quelque chose comme ça:pkg_resources.resource_string('pkg_name', 'data/file.txt')
ankostis
@ankostis setuptools est assez intelligent pour extraire l'archive s'il détecte que vous avez utilisé __file__quelque part. Dans mon cas, j'utilise une bibliothèque qui veut vraiment des chemins et non des flux. Bien sûr, je pourrais écrire les fichiers temporairement sur le disque, mais étant paresseux, j'utilise simplement la fonctionnalité de setuptools.
letmaik