web-dev-qa-db-fra.com

Comment lire un fichier (statique) à l'intérieur d'un package Python?

Pourriez-vous me dire comment puis-je lire un fichier qui se trouve dans mon Python?

Ma situation

Un package que je charge possède 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 d'accès à ce 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?

62
ronszon

TLDR; Utilisez le module importlib.resources De la bibliothèque standard comme expliqué dans la méthode n ° 2 ci-dessous.

Le traditionnelpkg_resources De setuptools n'est plus recommandé en raison des performances les raisons .
J'ai conservé la liste traditionnelle en premier, pour expliquer les différences avec la nouvelle méthode lors du portage du code existant (portage également expliqué ici ).


Supposons que vos modèles se trouvent dans un dossier imbriqué dans le package de votre module:

  <your-package>
    +--<module-asking-the-file>
    +--templates/
          +--temp_file                         <-- We want this file.

Note 1: Bien sûr, nous ne devons PAS jouer avec l'attribut __file__ (Par exemple, le code se cassera lorsqu'il sera servi à partir d'un Zip).

Note 2: Si vous construisez ce package, n'oubliez pas de déclatrer vos fichiers de données comme package_data Ou data_files dans votre setup.py.

1) Utilisation de pkg_resources À partir de setuptools (lent)

Vous pouvez utiliser pkg_resources package de la distribution setuptools, mais qui a un coût, en termes de performances:

import pkg_resources

# Could be any dot-separated package/module name or a "Requirement"
resource_package = __name__
resource_path = '/'.join(('templates', 'temp_file'))  # Do not use os.path.join()
template = pkg_resources.resource_string(resource_package, resource_path)
# or for a file-like stream:
template = pkg_resources.resource_stream(resource_package, resource_path)

Conseils:

  • Cela lira les données même si votre distribution est compressée, vous pouvez donc définir Zip_safe=True Dans votre setup.py, Et/ou utiliser le très attendu zipapp packer from python-3.5 pour créer des distributions autonomes.

  • N'oubliez pas d'ajouter setuptools dans vos exigences d'exécution (par exemple dans install_requires`).

... et notez que selon les documents Setuptools/pkg_resources, vous ne devez pas utiliser os.path.join:

Accès aux ressources de base

Notez que les noms de ressource doivent être des chemins séparés par / Et ne peuvent pas être absolus (c'est-à-dire aucun / En tête) ou contenir des noms relatifs comme "..". Utilisez pas utilisez les routines os.path Pour manipuler les chemins d'accès aux ressources, car ce sont pas chemins d'accès au système de fichiers.

2) Python> = 3.7, ou en utilisant la bibliothèque rétroportée importlib_resources

Utilisez le module importlib.resources de la bibliothèque standard plus efficace que setuptools, ci-dessus:

try:
    import importlib.resources as pkg_resources
except ImportError:
    # Try backported to PY<37 `importlib_resources`.
    import importlib_resources as pkg_resources

from . import templates  # relative-import the *package* containing the templates

template = pkg_resources.read_text(templates, 'temp_file')
# or for a file-like stream:
template = pkg_resources.open_text(templates, 'temp_file')

Attention:

Concernant la fonction read_text(package, resource):

  • package peut être une chaîne ou un module.
  • resource n'est plus un chemin, mais juste le nom de fichier de la ressource à ouvrir, dans un package existant; il peut ne pas contenir de séparateurs de chemin et ne pas avoir de sous-ressources (c'est-à-dire qu'il ne peut pas être un répertoire).

Pour l'exemple posé dans la question, il faut maintenant:

  • transformez <your_package>/templates/ en un package approprié, en y créant un fichier __init__.py vide,
  • maintenant nous pouvons utiliser une simple instruction (éventuellement relative) import (plus d'analyse des noms de package/module),
  • et demandez simplement resource_name = "temp_file" (pas de chemin).

Conseils:

  • Les choses deviennent intéressantes quand un nom de fichier réel est demandé avec path(), puisque maintenant les gestionnaires de contexte sont utilisés pour les fichiers créés temporairement (lire ceci ).
  • Ajoutez la bibliothèque rétroportée, conditionnellement pour les anciens Pythons, avec install_requires=[" importlib_resources ; python_version<'3.7'"] (Cochez ceci si vous empaquetez votre projet avec setuptools<36.2.1).
  • N'oubliez pas de supprimer la bibliothèque setuptools de votre runtime-requirements, si vous avez migré de la méthode traditionnelle.
  • Vous pouvez également définir Zip_safe=True Dans votre setup.py.
116
ankostis

Si vous avez cette structure

lidtk
├── bin
│   └── lidtk
├── lidtk
│   ├── analysis
│   │   ├── char_distribution.py
│   │   └── create_cm.py
│   ├── classifiers
│   │   ├── char_dist_metric_train_test.py
│   │   ├── char_features.py
│   │   ├── cld2
│   │   │   ├── cld2_preds.txt
│   │   │   └── cld2wili.py
│   │   ├── get_cld2.py
│   │   ├── text_cat
│   │   │   ├── __init__.py
│   │   │   ├── REAMDE.md   <---------- say you want to get this
│   │   │   └── textcat_ngram.py
│   │   └── tfidf_features.py
│   ├── data
│   │   ├── __init__.py
│   │   ├── create_ml_dataset.py
│   │   ├── download_documents.py
│   │   ├── language_utils.py
│   │   ├── pickle_to_txt.py
│   │   └── wili.py
│   ├── __init__.py
│   ├── get_predictions.py
│   ├── languages.csv
│   └── utils.py
├── README.md
├── setup.cfg
└── setup.py

vous avez besoin de ce code:

import pkg_resources

# __in case you're within the package
# - otherwise it would be 'lidtk' in this example as it is the package name
path = 'classifiers/text_cat/REAMDE.md'  # always use slash
filepath = pkg_resources.resource_filename(__name__, path)

Je ne suis pas trop sûr de la partie "toujours utiliser une barre oblique". Cela peut provenir de setuptools

Notez également que si vous utilisez des chemins, vous devez utiliser une barre oblique (/) comme séparateur de chemin, même si vous êtes sous Windows. Setuptools convertit automatiquement les barres obliques en séparateurs spécifiques à la plate-forme au moment de la construction

Si vous vous demandez où se trouve la documentation:

11
Martin Thoma

Le contenu de "10.8. Lecture de fichiers de données dans un package" de Python Cookbook, Third Edition par David Beazley et Brian K. Jones donnant les réponses.

Je vais le faire ici:

Supposons que vous ayez un package avec des fichiers organisés comme suit:

mypackage/
    __init__.py
    somedata.dat
    spam.py

Supposons maintenant que le fichier spam.py veuille lire le contenu du fichier somedata.dat. Pour ce faire, utilisez le code suivant:

import pkgutil
data = pkgutil.get_data(__package__, 'somedata.dat')

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 soit le fournir directement, soit 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 à l'aide des 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.

5
chaokunyang

Chaque module python dans votre package a un __file__ attribut

Vous pouvez l'utiliser comme:

import os 
from mypackage

templates_dir = os.path.join(os.path.dirname(mypackage.__file__), 'templates')
template_file = os.path.join(templates_dir, 'template.txt')

Pour les ressources Egg, voir: http://peak.telecommunity.com/DevCenter/PythonEggs#accessing-package-resources

3
Zaur Nasibov

en supposant que vous utilisez un fichier Egg; 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, depuis que j'ai travaillé avec __path__[0] peut parfois mal tourner (je ne me souviens pas du nom, mais j'ai parcouru au moins une bibliothèque, qui a ajouté quelque chose devant cette liste!).

De plus, les fichiers Egg sont généralement extraits à la volée vers un emplacement temporaire appelé "cache d'oeufs". Vous pouvez modifier cet emplacement à l'aide d'une variable d'environnement, soit avant de démarrer votre script, soit plus tard, par exemple.

os.environ['PYTHON_Egg_CACHE'] = path

Cependant il y a pkg_resources qui pourrait faire le travail correctement.

0
Florian