Zugriff auf Daten im Paket-Unterverzeichnis

130

Ich schreibe ein Python-Paket mit Modulen, die Datendateien in einem ./data/Unterverzeichnis öffnen müssen . Im Moment habe ich die Pfade zu den Dateien fest in meine Klassen und Funktionen codiert. Ich möchte robusteren Code schreiben, der auf das Unterverzeichnis zugreifen kann, unabhängig davon, wo es auf dem System des Benutzers installiert ist.

Ich habe verschiedene Methoden ausprobiert, aber bisher hatte ich kein Glück. Es scheint, dass die meisten Befehle "aktuelles Verzeichnis" das Verzeichnis des Python-Interpreters des Systems und nicht das Verzeichnis des Moduls zurückgeben.

Dies scheint ein triviales, häufiges Problem zu sein. Trotzdem kann ich es nicht herausfinden. Ein Teil des Problems besteht darin, dass meine Datendateien keine .pyDateien sind, sodass ich keine Importfunktionen und dergleichen verwenden kann.

Irgendwelche Vorschläge?

Im Moment sieht mein Paketverzeichnis so aus:

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

Ich versuche , den Zugang data.txtvon module*.py!

Jacob Lyles
quelle

Antworten:

24

Sie können __file__den Pfad zum Paket folgendermaßen abrufen:

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
quelle
44
Dies funktioniert nicht, wenn sich die Dateien in einer Distribution befinden (IE. Egg). Verwenden Sie pkg_resources, um an die Datendatei zu gelangen.
Chris
2
In der Tat ist dies kaputt.
Federico
1
Funktioniert auch __file__nicht mit py2exe, da der Wert der Pfad zur Zip-Datei ist.
Pod
1
Das hat bei mir tatsächlich funktioniert. Hatte keine Probleme. Ich benutze Python 3.6
Jorge
1
Dies funktioniert bei Verteilung (Ei usw.) nicht.
Adarsh ​​Trivedi
166

Die Standardmethode hierfür sind setuptools-Pakete und pkg_resources.

Sie können Ihr Paket gemäß der folgenden Hierarchie anordnen und die Paket-Setup-Datei so konfigurieren, dass sie über diesen Link auf Ihre Datenressourcen verweist:

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

Sie können diese Dateien dann mithilfe von pkg_resources gemäß diesem Link erneut finden und verwenden:

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
quelle
7
Wird nicht pkg_resources eine Laufzeitabhängigkeit erstellen Setuptools ? Zum Beispiel verteile ich ein Debian-Paket neu. Warum sollte ich mich python-setuptoolsnur darauf verlassen? Bisher __file__funktioniert gut für mich.
Mlt
4
Warum dies besser ist: Die ResourceManager-Klasse bietet einheitlichen Zugriff auf Paketressourcen, unabhängig davon, ob diese Ressourcen als Dateien und Verzeichnisse vorhanden sind oder in einem Archiv komprimiert werden
vrdhn
4
Genialer Vorschlag, danke. Ich implementierte eine Standarddatei, die mitfrom pkg_resources import resource_filename open(resource_filename('data', 'data.txt'), 'rb')
eifriganalyst
5
Wie funktioniert dies für die Verwendung des Pakets, wenn es nicht installiert ist? Ich meine, ich
teste
11
In Python 3.7, importlib.resourcesersetzt pkg_resourcesfür diesen Zweck (wegen der Performance - Probleme).
Benjamin
13

Um eine Lösung zu bieten, die heute funktioniert. Verwenden Sie diese API auf jeden Fall, um nicht alle diese Räder neu zu erfinden.

Ein echter Dateisystem-Dateiname wird benötigt. Gezippte Eier werden in ein Cache-Verzeichnis extrahiert:

from pkg_resources import resource_filename, Requirement

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

Gibt ein lesbares dateiähnliches Objekt für die angegebene Ressource zurück. Es kann sich um eine tatsächliche Datei, ein StringIO oder ein ähnliches Objekt handeln. Der Stream befindet sich im "Binärmodus" in dem Sinne, dass alle Bytes in der Ressource unverändert gelesen werden.

from pkg_resources import resource_stream, Requirement

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

Paketerkennung und Ressourcenzugriff mit pkg_resources

Sascha Gottfried
quelle
10

Es macht oft keinen Sinn, eine Antwort zu geben, die Details des Codes enthält, der nicht so funktioniert, wie er ist, aber ich glaube, dass dies eine Ausnahme ist. Python 3.7 hinzugefügt importlib.resources, das ersetzen soll pkg_resources. Es würde für den Zugriff auf Dateien in Paketen funktionieren, deren Namen keine Schrägstriche enthalten , d. H.

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

dh Sie könnten zum Beispiel mit data2.txtinnerhalb des Pakets zugreifenfoo

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

aber es würde mit einer Ausnahme für scheitern

>>> 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

Dies kann nicht festgelegt werden , außer indem man __init__.pyin dataund dann als Paket mit:

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

Der Grund für dieses Verhalten ist "es ist beabsichtigt" ; aber das Design könnte sich ändern ...

Antti Haapala
quelle
Haben Sie einen besseren Link für "it is by design" als ein Youtube-Video - vorzugsweise eines mit Text?
Gerrit
@gerrit der 2. enthält Text. "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

Sie benötigen einen Namen für Ihr gesamtes Modul. Sie erhalten einen Verzeichnisbaum, in dem dieses Detail nicht aufgeführt ist. Bei mir hat dies funktioniert:

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

Insbesondere setuptools scheint keine Dateien aufzulösen, die auf einer Namensübereinstimmung mit gepackten Datendateien basieren. Sie müssen also das data/Präfix so ziemlich einfügen, egal was passiert . Sie können verwenden, os.path.join('data', 'data.txt)wenn Sie alternative Verzeichnis-Trennzeichen benötigen. Im Allgemeinen finde ich jedoch keine Kompatibilitätsprobleme mit fest codierten Verzeichnis-Trennzeichen im Unix-Stil.

ThorSummoner
quelle
docs.python.org/3.6/distutils/… > Beachten Sie, dass alle im Setup-Skript angegebenen Pfadnamen (Dateien oder Verzeichnisse) nach der Unix-Konvention geschrieben werden sollten, dh durch Schrägstriche getrennt. Die Distutils kümmern sich darum, diese plattformneutrale Darstellung in das zu konvertieren, was auf Ihrer aktuellen Plattform angemessen ist, bevor Sie den Pfadnamen tatsächlich verwenden. Dies macht Ihr Setup-Skript betriebssystemübergreifend portierbar, was natürlich eines der Hauptziele der Distutils ist. In diesem Sinne sind alle Pfadnamen in diesem Dokument durch Schrägstriche getrennt.
Changyuheng
6

Ich glaube, ich habe eine Antwort gefunden.

Ich erstelle ein Modul data_path.py, das ich in meine anderen Module importiere, die Folgendes enthalten:

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

Und dann öffne ich alle meine Dateien mit

open(os.path.join(data_path,'filename'), <param>)
Jacob Lyles
quelle
2
Dies funktioniert nicht, wenn sich die Ressource in einer Archivverteilung befindet (z. B. in einem Ei mit Reißverschluss). Bevorzugen Sie so etwas:pkg_resources.resource_string('pkg_name', 'data/file.txt')
Ankostis
@ankostis setuptools ist clever genug, um das Archiv zu extrahieren, wenn es feststellt, dass Sie es __file__irgendwo verwendet haben. In meinem Fall verwende ich eine Bibliothek, die wirklich Pfade und keine Streams will. Natürlich könnte ich die Dateien vorübergehend auf die Festplatte schreiben, aber da ich faul bin, verwende ich nur die Funktion von setuptools.
Letmaik