Python: Importieren eines Unterpakets oder Untermoduls

88

Nachdem ich bereits Flat-Pakete verwendet hatte, hatte ich das Problem mit verschachtelten Paketen nicht erwartet. Hier ist…

Verzeichnislayout

dir
 |
 +-- test.py
 |
 +-- package
      |
      +-- __init__.py
      |
      +-- subpackage
           |
           +-- __init__.py
           |
           +-- module.py

Inhalt von init .py

Beide package/__init__.pyund package/subpackage/__init__.pysind leer.

Inhalt von module.py

# file `package/subpackage/module.py`
attribute1 = "value 1"
attribute2 = "value 2"
attribute3 = "value 3"
# and as many more as you want...

Inhalt von test.py(3 Versionen)

Version 1

# file test.py
from package.subpackage.module import *
print attribute1 # OK

Das ist die schlechte und unsichere Art, Dinge zu importieren (alles in großen Mengen importieren), aber es funktioniert.

Version 2

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
from module import attribute1

Eine sicherere Möglichkeit, Artikel für Artikel zu importieren, aber dies schlägt fehl. Python möchte dies nicht: schlägt mit der Meldung fehl: "Kein Modul mit dem Namen Modul". Jedoch …

# file test.py
import package.subpackage.module
from package.subpackage import module # Alternative
print module # Surprise here

… Sagt <module 'package.subpackage.module' from '...'>. Das ist also ein Modul, aber das ist kein Modul / -P 8-O ... ähm

Version 3

# file test.py v3
from package.subpackage.module import attribute1
print attribute1 # OK

Dieser funktioniert. Sie sind also gezwungen, entweder ständig das Overkill-Präfix zu verwenden oder den unsicheren Weg wie in Version 1 zu verwenden, und von Python nicht zugelassen, den sicheren praktischen Weg zu verwenden? Der bessere Weg, der sicher ist und unnötiges langes Präfix vermeidet, ist der einzige, den Python ablehnt? Liegt es daran, dass es liebt import *oder dass es überlange Präfixe liebt (was nicht dazu beiträgt, diese Praxis durchzusetzen)?

Entschuldigen Sie die harten Worte, aber das sind zwei Tage, an denen ich versuche, dieses dumme Verhalten zu umgehen. Wenn ich mich nicht irgendwo völlig geirrt habe, habe ich das Gefühl, dass etwas in Pythons Modell von Paketen und Unterpaketen wirklich kaputt ist.

Anmerkungen

  • Ich möchte mich nicht auf sys.pathglobale Nebenwirkungen verlassen , um globale Nebenwirkungen zu vermeiden, und auch nicht auf *.pthDateien, die nur eine andere Möglichkeit sind, sys.pathmit denselben globalen Effekten zu spielen . Damit die Lösung sauber ist, muss sie nur lokal sein. Entweder ist Python in der Lage, Unterpakete zu verarbeiten, oder auch nicht, aber es sollte nicht erforderlich sein, mit der globalen Konfiguration zu spielen, um lokale Inhalte verarbeiten zu können.
  • Ich habe auch versucht, Importe zu verwenden package/subpackage/__init__.py, aber es hat nichts gelöst, es macht dasselbe und beschwert sich, dass subpackagees kein bekanntes Modul ist, während es print subpackagesagt , dass es ein Modul ist (wieder seltsames Verhalten).

Vielleicht bin ich völlig falsch hart (die Option, die ich bevorzugen würde), aber das macht mich sehr enttäuscht über Python.

Gibt es einen anderen bekannten Weg neben den drei, die ich versucht habe? Etwas, von dem ich nichts weiß?

(Seufzer)

-----% <----- edit ----->% -----

Fazit bisher (nach den Kommentaren der Leute)

In Python gibt es nichts Besseres als ein echtes Unterpaket, da alle Paketreferenzen nur auf ein globales Wörterbuch verweisen. Dies bedeutet, dass es kein lokales Wörterbuch gibt, was bedeutet, dass es keine Möglichkeit gibt, lokale Paketreferenzen zu verwalten.

Sie müssen entweder das vollständige Präfix oder das kurze Präfix oder den Alias ​​verwenden. Wie in:

Vollständige Präfixversion

from package.subpackage.module import attribute1
# An repeat it again an again
# But after that, you can simply:
use_of (attribute1)

Kurze Präfixversion (aber wiederholtes Präfix)

from package.subpackage import module
# Short but then you have to do:
use_of (module.attribute1)
# and repeat the prefix at every use place

Oder eine Variation der oben genannten.

from package.subpackage import module as m
use_of (m.attribute1)
# `m` is a shorter prefix, but you could as well
# define a more meaningful name after the context

Faktorisierte Version

Wenn es Ihnen nichts ausmacht, mehrere Entitäten gleichzeitig in einem Stapel zu importieren, können Sie:

from package.subpackage.module import attribute1, attribute2
# and etc.

Nicht in meinem ersten Lieblingsgeschmack (ich bevorzuge eine Importanweisung pro importierter Entität), aber möglicherweise die, die ich persönlich bevorzugen werde.

Update (14.09.2012):

Schließlich scheint es in der Praxis in Ordnung zu sein, außer mit einem Kommentar zum Layout. Anstelle der oben genannten habe ich verwendet:

from package.subpackage.module import (

    attribute1, 
    attribute2,
    attribute3,
    ...)  # and etc.
Hibou57
quelle
Wie läuft es, wenn Sie "from. Import module" in "/package/subpackage/__init__.py" schreiben?
Markus Unterwaditzer
Ihre "faktorisierte Version" scheint genau richtig für das zu sein, was Sie tun möchten. Wenn Sie eine separate Importzeile für Attribut1 und Attribut2 ausführen (wie Sie es "bevorzugen"), geben Sie sich nur absichtlich mehr Arbeit. Dafür gibt es keinen Grund.
BrenBarn
Entschuldigung, aber ich verstehe nicht, was Sie wollen. Könnten Sie Ihre Frage klarer formulieren? Was möchten Sie genau tun? Ich meine, was möchten Sie schreiben, das nicht funktioniert, und wie würden Sie erwarten, dass es funktioniert? Nach dem, was ich gelesen habe, denke ich, dass die Semantik des Imports wie die von Java oder vielleicht von C ist. Letzte Sache: Sie können ein Modul "Sternimport" sicher machen, indem Sie eine __all__Variable hinzufügen , die eine Liste der Namen enthält, die beim Sternimport exportiert werden sollen. edit: Okay, beim Lesen der BrenBarn-Antwort habe ich verstanden, was du meinst.
Bakuriu

Antworten:

66

Sie scheinen falsch zu verstehen, wie importnach Modulen gesucht wird. Wenn Sie eine Importanweisung verwenden, wird immer der tatsächliche Modulpfad (und / oder sys.modules) durchsucht . es macht keinen Gebrauch von Modul machen Objekte im lokalen Namespace, der wegen der früheren Einfuhren existieren. Wenn Sie das tun:

import package.subpackage.module
from package.subpackage import module
from module import attribute1

In der zweiten Zeile wird nach einem aufgerufenen Paket gesucht package.subpackageund moduleaus diesem Paket importiert . Diese Zeile hat keine Auswirkung auf die dritte Zeile. Die dritte Zeile sucht nur nach einem aufgerufenen Modul moduleund findet keines. Das aufgerufene Objekt, moduledas Sie aus der obigen Zeile erhalten haben, wird nicht "wiederverwendet" .

Mit anderen Worten from someModule import ...bedeutet nicht "von dem Modul namens someModule, das ich zuvor importiert habe ...", sondern "von dem Modul namens someModule, das Sie auf sys.path finden ...". Es gibt keine Möglichkeit, den Pfad eines Moduls "inkrementell" aufzubauen, indem die Pakete importiert werden, die dazu führen. Sie müssen sich beim Importieren immer auf den gesamten Modulnamen beziehen.

Es ist nicht klar, was Sie erreichen wollen. Wenn Sie nur das bestimmte Objektattribut1 importieren möchten, tun Sie es einfach from package.subpackage.module import attribute1und fertig. Sie müssen sich keine Gedanken mehr darüber machen, wie lange package.subpackage.moduleSie den gewünschten Namen importiert haben.

Wenn Sie noch Zugriff haben später andere Namen auf das Modul zugreifen möchten, dann können Sie tun from package.subpackage import moduleund wie Sie Sie dann tun können , gesehen haben module.attribute1und so weiter , so viel wie Sie möchten.

Wenn Sie beides möchten - das heißt, wenn Sie attribute1direkt zugänglich sein möchten und Sie modulezugänglich sein möchten , führen Sie einfach beide oben genannten Schritte aus:

from package.subpackage import module
from package.subpackage.module import attribute1
attribute1 # works
module.someOtherAttribute # also works

Wenn Sie nicht package.subpackageeinmal zweimal tippen möchten, können Sie einfach manuell einen lokalen Verweis auf Attribut1 erstellen:

from package.subpackage import module
attribute1 = module.attribute1
attribute1 # works
module.someOtherAttribute #also works
BrenBarn
quelle
Ihre Kommentare gehen in die gleiche Richtung wie die von Ignacio Vazquez-Abrams (ich habe seine Nachricht kommentiert). Ihr Zusatz am Ende über die Verwendung module.attribute1ist etwas , über das ich nachgedacht habe, aber ich denke, es gäbe eine Möglichkeit, die Notwendigkeit eines Präfixes überall zu vermeiden. Ich muss also entweder überall ein Präfix verwenden oder einen lokalen Alias ​​erstellen und den Namen wiederholen. Nicht der Stil, den ich erwartet hatte, aber wenn es keinen Weg gibt (schließlich bin ich an Ada gewöhnt, was mit seinen Umbenennungserklärungen etwas Ähnliches erfordert).
Hibou57
@ Hibou57: Mir ist immer noch nicht klar, was Sie mit Ihrer "Version 2" erreichen wollen. Was möchten Sie tun, das ist nicht möglich? Sie möchten niemals einen Teil des Paket- / Modul- / Attributnamens erneut eingeben, aber dennoch sowohl das Modul als auch sein Attribut importieren?
BrenBarn
Ich wollte eine lokale Paketreferenz haben, genau wie Sie eine lokale Objektreferenz haben können. Es scheint, dass es endlich wirklich lokale Modulreferenzen gibt, aber Sie können nicht aus diesen importieren. Es ist eine Mischung aus lokal und global mit einem lustigen Geschmack (einige Dinge können lokal sein, andere müssen global sein, ich mag es nicht, aber ich bin in Ordnung, solange ich jetzt besser verstehe, wie es funktioniert). Danke übrigens für deine Nachricht.
Hibou57
1
Ich bin mir nicht sicher, ob Sie noch verstehen, wie es funktioniert. Oder dass du es 2012 auf jeden Fall getan hast.
Hejazzman
1
Jedes Mal, wenn ich nach einer 6-monatigen Entlassung zu Python zurückkehre, lande ich hier. Wenn ich nur jedes Mal, wenn ich diese Seite besuche, eine positive Bewertung abgeben könnte! Ich werde ein gigantisches Poster mit diesem Satz erstellen: "Es gibt keine Möglichkeit, den Pfad eines Moduls" schrittweise "aufzubauen, indem die Pakete importiert werden, die dazu führen."
PatrickT
10

Der Grund, warum # 2 fehlschlägt, ist, dass sys.modules['module']es keine gibt (die Importroutine hat einen eigenen Bereich und kann den modulelokalen Namen nicht sehen ) und es kein moduleModul oder Paket auf der Festplatte gibt. Beachten Sie, dass Sie mehrere importierte Namen durch Kommas trennen können.

from package.subpackage.module import attribute1, attribute2, attribute3

Ebenfalls:

from package.subpackage import module
print module.attribute1
Ignacio Vazquez-Abrams
quelle
Ihr Hinweis, sys.modules['name']den ich bis jetzt nicht kannte, ließ mich denken, dass ich Angst hatte (und BrenBarn bestätigt): Es gibt nichts Schöneres als echte Unterpakete in Python. sys.modulesWie der Name schon sagt, ist es global, und wenn alle Verweise auf Module davon abhängen, gibt es nichts Besseres als einen lokalen Verweis auf ein Modul (möglicherweise mit Python 3.x?).
Hibou57
Ihre Verwendung von "Referenz" dort ist nicht eindeutig; Der erste importin # 2 generiert eine lokale Referenz, an die package.subpackage.modulegebunden werden soll module.
Ignacio Vazquez-Abrams
Ja, aber das ist ein "Modul", aus dem ich nicht importieren kann
;-)
0

Wenn Sie nur versuchen, attribute1 in Ihrem globalen Namespace abzurufen, scheint Version 3 in Ordnung zu sein. Warum ist es Overkill-Präfix?

In Version 2 anstelle von

from module import attribute1

du kannst tun

attribute1 = module.attribute1
Thomas Vander Stichele
quelle
attribute1 = module.attribute1wiederholt nur den Namen ohne Mehrwert. Ich weiß, dass es funktioniert, aber ich mag diesen Stil nicht (was nicht bedeutet, dass ich Ihre Antwort nicht mag).
Hibou57
2
Ich glaube, ich verstehe, wie alle Leute, die hier kommentieren, nicht, was Sie tun wollen. In allen Beispielen, die Sie geben, möchten Sie anscheinend ein Symbol aus einem Unterpaket in Ihrem Namespace erhalten. In Ihrem nicht funktionierenden Beispiel (Beispiel 2) möchten Sie dazu ein Submodul aus einem Paket importieren und anschließend ein Symbol aus diesem Submodul importieren. Ich weiß nicht, warum Sie es in zwei Schritten anstatt in einem tun wollen. Erklären Sie vielleicht mehr, was Ihre ideale Lösung wäre und warum.
Thomas Vander Stichele