Kombinieren mehrerer Pakete in einem einzigen Python-Namespace

Manchmal ist es erforderlich, mehrere Pakete, die im selben Namespace liegen, auf unterschiedlichen physischen Pfaden zu trennen. Wenn Sie beispielsweise verschiedene Layouts von Plugins übertragen möchten, diese anschließend hinzufügen können, ohne ihren Speicherort zu steuern, und gleichzeitig über einen Namespace auf sie zugreifen können.

Dieser Spickzettel, der eher für Anfänger geeignet ist, ist Python-Namespaces gewidmet.

Schauen wir uns an, wie dies in verschiedenen Versionen von Python möglich ist, da Python2 zwar bald nicht mehr unterstützt wird, sich viele von uns jedoch gerade zwischen zwei Bränden befinden. Dies ist nur eine der wichtigen Nuancen beim Übergang.

Bild

Betrachten Sie dieses Beispiel:

Wir wollen die Paketstruktur bekommen:

namespace1 package1 module1 package2 module2 

Inhalt der Modul1-Datei

 print('package 1') var1 = 1 

Inhalt der Modul2-Datei

 print('package 2') var2 = 2 

Gleichzeitig werden Pakete in der folgenden Ordnerstruktur verteilt:

  path1 namespace1 package1 module1 path2 namespace1 package2 module2 

Angenommen, Pfad1 und Pfad2 sind bereits zu sys.path hinzugefügt. Wir müssen auf Modul1 und Modul2 zugreifen:

  from namespace1.package1 import module1 from namespace1.package2 import module2 

Was passiert in Python 3.7, wenn dieser Code ausgeführt wird? Alles funktioniert wunderbar:

 package 1 package 2 

Mit PEP-420 in Python 3.3 wurde die Unterstützung impliziter Namespaces angezeigt. Außerdem müssen Sie beim Importieren eines Pakets von py33 keine __init__.py-Dateien erstellen. Und beim Importieren von Namespace ist es nur _ verboten_. Wenn die Datei __init__.py in einem oder beiden Verzeichnissen mit dem Namen name1 vorhanden ist, tritt beim Import des zweiten Pakets ein Fehler auf.

 ModuleNotFoundError: No module named 'namespace1.package2' 

Das Vorhandensein eines Insiders bestimmt also explizit das Paket, und Pakete können nicht kombiniert werden, es ist eine einzelne Entität. Wenn Sie unabhängig von der alten Entwicklung ein neues Projekt starten und die Pakete mit pip installiert werden, müssen Sie sich an diese Methode halten. Manchmal erben wir jedoch den alten Code, der zumindest für eine Weile beibehalten oder auf eine neue Version portiert werden muss.

Fahren wir mit Python 2.7 fort . Bei dieser Version ist es bereits interessanter, dass Sie zuerst __init__.py zu jedem Verzeichnis hinzufügen müssen, um Pakete zu erstellen. Andernfalls erkennt der Interpreter das Paket in diesem Satz von Dateien einfach nicht. Schreiben Sie dann eine explizite Namespace-Deklaration in __init__-Dateien, die sich auf Namespace1 beziehen. Andernfalls wird nur das erste Paket importiert.

 from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

Was passiert damit? Wenn der Interpreter den ersten Import erreicht, wird ein Paket mit demselben Namen in sys.path durchsucht, es befindet sich in path1 / namespace1 und der Interpreter führt path1 / namespace1 / __ init__.py aus. Weitere Suche wird nicht durchgeführt. Die Funktion extens_path selbst durchsucht jedoch bereits sys.path, findet alle Pakete mit dem Namen namespace1 und dem internen Namen und fügt sie der Variablen __path__ des Pakets namespace1 hinzu, mit der in diesem Namespace nach untergeordneten Paketen gesucht wird.

In offiziellen Handbüchern wird empfohlen, dass die Initialen bei jedem Platzieren von Namespace1 gleich sind. Tatsächlich können sie alle bis auf die erste leer sein, die während einer Suche in sys.path gefunden wird, in der pkgutil.extend_path aufgerufen werden soll, da der Rest nicht ausgeführt wird. Es ist jedoch natürlich besser, dass der Anruf wirklich in jedem Team erfolgt, um Ihre Logik nicht „für den Fall“ zu verknüpfen und nicht zu erraten, welches Team als erstes ausgeführt wurde, da sich die Suchreihenfolge ändern kann. Aus dem gleichen Grund sollten Sie keine anderen logischen __init__- Dateien im Variablenbereich platzieren.

Dies wird in zukünftigen Versionen funktionieren und dieser Code kann zum Schreiben von kompatiblem Code verwendet werden. Beachten Sie jedoch, dass Sie die gewählte Methode in jedem verteilten Paket einhalten müssen. Wenn Sie in Version 3 bei einem Aufruf von pkgutil.extend_path einen Posteingang in einige Pakete einfügen und einige ohne Posteingang belassen, funktioniert dies nicht.
Darüber hinaus eignet sich diese Option auch für den Fall, dass Sie die Installation mit python setup.py installieren möchten.

Ein anderer Weg, der jetzt als etwas veraltet gilt, aber immer noch viel zu finden ist, wo:

 #namespace1/__init__.py __import__('pkg_resources').declare_namespace(__name__) 

Das Modul pkg_resources wird mit dem Paket setuptools geliefert. Hier ist die Bedeutung dieselbe wie in pkgutil - es ist erforderlich, dass jede __init__-Datei an jedem Speicherort von Namespace1 dieselbe Namespace-Deklaration enthält und es keinen anderen Code gibt. Gleichzeitig muss der Namespace namespace_packages = ['namespace1'] in setup.py registriert werden. Eine detailliertere Beschreibung des Erstellens von Paketen würde den Rahmen dieses Artikels sprengen.

Außerdem finden Sie häufig einen solchen Code

 try: __import__('pkg_resources').declare_namespace(__name__) except: from pkgutil import extend_path __path__ = extend_path(__path__, __name__) 

Hier ist die Logik einfach: Wenn setuptools nicht installiert ist, verwenden wir pkgutil, das in der Standardbibliothek enthalten ist.

Wenn Sie einen Namespace auf eine dieser Arten konfigurieren, können Sie einen anderen von einem Modul aus aufrufen. Ändern Sie beispielsweise den Namespace1 / package2 / module2

 import namespace1.package1.module1 print(var1) 

Und dann werden wir sehen, was passiert, wenn wir fälschlicherweise ein neues und ein vorhandenes Paket benennen und es mit demselben Namespace umschließen. Beispielsweise gibt es zwei Pakete an verschiedenen Orten mit dem Namen package1.

 namespace1 package1 module1 package1 module2 

In diesem Fall wird nur der erste importiert und es besteht kein Zugriff auf Modul2. Pakete können nicht kombiniert werden.

 from namespace1.package1 import module1 from namespace1.package1 import module2 #>>ImportError: cannot import name module2 

Zusammenfassung:

  1. Für Python, das älter als 3.3 ist und mit pip installiert wird, wird empfohlen, eine implizite Namespace-Deklaration zu verwenden.
  2. Bei Unterstützung der Versionen 2 und 3 sowie bei der Installation mit pip und python setup.py wird die Option mit pkgutil empfohlen.
  3. Die Option pkg_resources wird empfohlen, wenn Sie ältere Pakete mit dieser Methode unterstützen müssen oder wenn das Paket zip-sicher sein soll.

Quellen:


Beispiele finden Sie hier .

Source: https://habr.com/ru/post/de458432/


All Articles