Wie erstelle ich ein Namespace-Paket in Python?

141

In Python können Sie mit einem Namespace-Paket Python-Code auf mehrere Projekte verteilen. Dies ist nützlich, wenn Sie verwandte Bibliotheken als separate Downloads freigeben möchten. Zum Beispiel mit den Verzeichnissen Package-1und Package-2in PYTHONPATH,

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

Der Endbenutzer kann import namespace.module1und import namespace.module2.

Wie kann ein Namespace-Paket am besten definiert werden, damit mehr als ein Python-Produkt Module in diesem Namespace definieren kann?

Joeforker
quelle
5
Für mich sind Modul1 und Modul2 eher Unterpakete als Module. Nach meinem Verständnis ist ein Modul im Grunde eine einzelne Datei. Vielleicht wären subpkg1 und subpkg2 als Namen sinnvoller?
Alan

Antworten:

79

TL; DR:

Unter Python 3.3 müssen Sie nichts tun, nur keine __init__.pyin Ihre Namespace-Paketverzeichnisse einfügen, und es wird einfach funktionieren. Wählen Sie vor Version 3.3 die pkgutil.extend_path()Lösung aus pkg_resources.declare_namespace(), da sie zukunftssicher und bereits mit impliziten Namespace-Paketen kompatibel ist.


Python 3.3 führt implizite Namespace-Pakete ein, siehe PEP 420 .

Dies bedeutet, dass es jetzt drei Objekttypen gibt, die von einem erstellt werden können import foo:

  • Ein Modul, das durch eine foo.pyDatei dargestellt wird
  • Ein reguläres Paket, dargestellt durch ein Verzeichnis, foodas eine __init__.pyDatei enthält
  • Ein Namespace-Paket, das durch ein oder mehrere Verzeichnisse fooohne __init__.pyDateien dargestellt wird

Pakete sind auch Module, aber hier meine ich "Nicht-Paket-Modul", wenn ich "Modul" sage.

Zuerst wird sys.pathnach einem Modul oder einem regulären Paket gesucht. Wenn dies erfolgreich ist, wird die Suche beendet und das Modul oder Paket erstellt und initialisiert. Wenn kein Modul oder reguläres Paket gefunden wurde, aber mindestens ein Verzeichnis gefunden wurde, wird ein Namespace-Paket erstellt und initialisiert.

Module und reguläre Pakete haben __file__die .pyDatei festgelegt, aus der sie erstellt wurden. Reguläre Pakete und Namespace-Pakete haben __path__das Verzeichnis oder die Verzeichnisse festgelegt, aus denen sie erstellt wurden.

Wenn Sie das tun import foo.bar, geschieht das über den Such zuerst foo, dann , wenn ein Paket gefunden wurde, für die Suche barmit getan wird foo.__path__als Suchpfad statt sys.path. If foo.barwird gefunden foound foo.barerstellt und initialisiert.

Wie mischen sich reguläre Pakete und Namespace-Pakete? Normalerweise nicht, aber die alte pkgutilexplizite Namespace-Paketmethode wurde um implizite Namespace-Pakete erweitert.

Wenn Sie ein reguläres Paket haben, das Folgendes hat __init__.py:

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

... das Legacy-Verhalten besteht darin, alle anderen regulären Pakete auf dem gesuchten Pfad zu seinem hinzuzufügen __path__. In Python 3.3 werden jedoch auch Namespace-Pakete hinzugefügt.

Sie können also die folgende Verzeichnisstruktur haben:

├── path1
   └── package
       ├── __init__.py
       └── foo.py
├── path2
   └── package
       └── bar.py
└── path3
    └── package
        ├── __init__.py
        └── baz.py

... und so lange , wie die beiden __init__.pydie haben extend_pathLinien (und path1, path2und path3sind in Ihrem sys.path) import package.foo, import package.barund import package.bazwird alle Arbeit.

pkg_resources.declare_namespace(__name__) wurde nicht aktualisiert, um implizite Namespace-Pakete einzuschließen.

klackern
quelle
2
Was ist mit Setuptools? Muss ich die namespace_packagesOption verwenden? Und das __import__('pkg_resources').declare_namespace(__name__)Ding?
Kawing-Chiu
3
Soll ich hinzufügen , namespace_packages=['package']in der setup.py?
Laurent LAPORTE
1
@clacke: Mit namespace_packages=['package']wird setup.py ein namespace_packages.txtim EGG-INFO hinzufügen . Ich kenne die Auswirkungen immer noch nicht…
Laurent LAPORTE
1
@ kawing-chiu Der Vorteil von pkg_resources.declare_namespaceover pkgutil.extend_pathist, dass es weiterhin überwacht sys.path. Auf diese Weise können sys.pathPakete im Namespace in diesem neuen Pfadelement weiterhin geladen werden, wenn ein neues Element hinzugefügt wird, nachdem ein Paket im Namespace zum ersten Mal geladen wurde. (Ein Vorteil der Verwendung von __import__('pkg_resources')Over import pkg_resourcesist, dass Sie nicht pkg_resourcesals ausgesetzt werden my_namespace_pkg.pkg_resources.)
Arthur Tacca
1
@clacke Es funktioniert nicht so (aber es hat den gleichen Effekt, als ob es so wäre). Es verwaltet eine globale Liste aller mit dieser Funktion erstellten Paketnamespaces und überwacht sys.path. Bei sys.pathÄnderungen wird überprüft, ob sich dies auf __path__einen Namespace auswirkt. Wenn dies der Fall ist, werden diese __path__Eigenschaften aktualisiert .
Arthur Tacca
81

Es gibt ein Standardmodul namens pkgutil , mit dem Sie Module an einen bestimmten Namespace anhängen können.

Mit der von Ihnen angegebenen Verzeichnisstruktur:

Package-1/namespace/__init__.py
Package-1/namespace/module1/__init__.py
Package-2/namespace/__init__.py
Package-2/namespace/module2/__init__.py

Sie sollten diese beiden Zeilen in beide Package-1/namespace/__init__.pyund Package-2/namespace/__init__.py(*) einfügen:

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

(* da - es sei denn, Sie geben eine Abhängigkeit zwischen ihnen an - Sie nicht wissen, welche von ihnen zuerst erkannt werden - siehe PEP 420 für weitere Informationen)

Wie die Dokumentation sagt:

Dadurch werden __path__alle Unterverzeichnisse der Verzeichnisse des Pakets hinzugefügt, die sys.pathnach dem Paket benannt sind.

Von nun an sollten Sie in der Lage sein, diese beiden Pakete unabhängig voneinander zu verteilen.

Mike Hordecki
quelle
17
Was sind die Vor- und Nachteile dieser Verwendung gegenüber dem Import __ ('pkg_resources'). Declare_namespace (__ name )?
Joeforker
14
Erstens __import__wird dies in diesem Fall als schlechter Stil angesehen, da er leicht durch eine einfache Importanweisung ersetzt werden kann. Genauer gesagt ist pkg_resources eine nicht standardmäßige Bibliothek. Es wird mit Setuptools geliefert, das ist also kein Problem. Schnelles Googeln zeigt, dass pkgutil in 2.5 eingeführt wurde und pkg_resources älter ist. Trotzdem ist pkgutil eine offiziell anerkannte Lösung. Die Aufnahme von pkg_resources wurde in PEP 365 tatsächlich abgelehnt.
Mike Hordecki
3
Zitat aus PEP 382 : Der derzeitige zwingende Ansatz für Namespace-Pakete hat zu mehreren leicht inkompatiblen Mechanismen für die Bereitstellung von Namespace-Paketen geführt. Zum Beispiel unterstützt pkgutil * .pkg-Dateien; setuptools nicht. Ebenso unterstützt setuptools das Überprüfen von Zip-Dateien und das Hinzufügen von Teilen zu seiner Variablen _namespace_packages, während pkgutil dies nicht tut.
Drake Guan
7
Sollten diese beiden Zeilen nicht in beide Dateien eingefügt werden: Package-1/namespace/__init__.py und Package-2/namespace/__init__.py vorausgesetzt, wir wissen nicht, welches Paketverzeichnis zuerst aufgeführt ist?
Bula
3
@ChristofferKarlsson Ja, das ist der Punkt, es ist in Ordnung, wenn Sie wissen, welches das erste ist, aber die eigentliche Frage ist, können Sie garantieren, dass es in jeder Situation das erste sein wird, dh für andere Benutzer?
Bula
5

Dieser Abschnitt sollte ziemlich selbsterklärend sein.

Kurz gesagt, geben Sie den Namespace-Code ein __init__.py, aktualisieren Sie ihn setup.py, um einen Namespace zu deklarieren, und Sie können loslegen.

iElectric
quelle
9
Sie sollten immer den relevanten Teil eines Links zitieren, falls der relevante Link nicht mehr funktioniert.
Tinned_Tuna
2

Dies ist eine alte Frage, aber jemand hat kürzlich in meinem Blog kommentiert, dass mein Beitrag über Namespace-Pakete immer noch relevant ist. Daher dachte ich, ich würde hier darauf verlinken, da er ein praktisches Beispiel dafür liefert, wie es funktioniert:

https://web.archive.org/web/20150425043954/http://cdent.tumblr.com/post/216241761/python-namespace-packages-for-tiddlyweb

Dieser Link zu diesem Artikel enthält die wichtigsten Informationen zu den Vorgängen:

http://www.siafoo.net/article/77#multiple-distributions-one-virtual-package

Der __import__("pkg_resources").declare_namespace(__name__)Trick ist, die Verwaltung von Plugins in TiddlyWeb so ziemlich voranzutreiben und scheint bisher zu funktionieren.

cdent
quelle
-9

Sie haben Ihre Python-Namespace-Konzepte von hinten nach vorne. In Python ist es nicht möglich, Pakete in Module einzufügen. Pakete enthalten Module, nicht umgekehrt.

Ein Python-Paket ist einfach ein Ordner, der eine __init__.pyDatei enthält. Ein Modul ist eine andere Datei in einem Paket (oder direkt auf dem PYTHONPATH), die eine .pyErweiterung hat. In Ihrem Beispiel haben Sie also zwei Pakete, aber keine Module definiert. Wenn Sie bedenken, dass ein Paket ein Dateisystemordner und ein Modul eine Datei ist, sehen Sie, warum Pakete Module enthalten und nicht umgekehrt.

In Ihrem Beispiel können Sie also davon ausgehen, dass Paket 1 und Paket 2 Ordner im Dateisystem sind, die Sie in den Python-Pfad eingefügt haben:

Package-1/
  namespace/
  __init__.py
  module1.py
Package-2/
  namespace/
  __init__.py
  module2.py

Sie haben jetzt ein Paket namespacemit zwei Modulen module1und module2. und wenn Sie keinen guten Grund haben, sollten Sie die Module wahrscheinlich in den Ordner legen und nur diesen auf dem Python-Pfad wie folgt haben:

Package-1/
  namespace/
  __init__.py
  module1.py
  module2.py
Tendayi Mawushe
quelle
Ich spreche über Dinge wie, zope.xwo eine Reihe verwandter Pakete als separate Downloads veröffentlicht werden.
Joeforker
Ok, aber welchen Effekt möchten Sie erzielen? Wenn sich die Ordner mit verwandten Paketen alle auf dem PYTHONPATH befinden, findet der Python-Interpreter sie ohne zusätzlichen Aufwand für Sie.
Tendayi Mawushe
5
Wenn Sie PYTHONPATH sowohl Paket-1 als auch Paket-2 hinzufügen, wird Python nur Paket-1 / Namespace / sehen.
Søren Løvborg