Warum benötigt Pythons __import__ fromlist?

75

Wenn Sie in Python ein Modul programmgesteuert importieren möchten, haben Sie folgende Möglichkeiten:

module = __import__('module_name')

Wenn Sie ein Submodul importieren möchten, denken Sie, es wäre eine einfache Sache von:

module = __import__('module_name.submodule')

Das funktioniert natürlich nicht. du kommst einfach module_namewieder. Du musst:

module = __import__('module_name.submodule', fromlist=['blah'])

Warum? Der tatsächliche Wert von fromlistscheint überhaupt keine Rolle zu spielen, solange er nicht leer ist. Was bringt es, ein Argument zu fordern und dann seine Werte zu ignorieren?

Die meisten Dinge in Python scheinen aus gutem Grund gemacht zu sein, aber für mein Leben kann ich keine vernünftige Erklärung dafür finden, dass dieses Verhalten existiert.

dh
quelle

Antworten:

127

In der Tat ist das Verhalten von __import__()ausschließlich auf die Implementierung der importAnweisung zurückzuführen, die aufruft __import__(). Es gibt im Grunde fünf leicht unterschiedliche Weise __import__()können aufgerufen werden import(mit zwei Hauptkategorien):

import pkg
import pkg.mod
from pkg import mod, mod2
from pkg.mod import func, func2
from pkg.mod import submod

Im ersten und zweiten Fall sollte die importAnweisung das Modulobjekt "ganz links" dem Namen "ganz links" zuweisen : pkg. Nachdem import pkg.modSie dies tun können, pkg.mod.func()weil die importAnweisung den lokalen Namen eingeführt hat pkg, der ein Modulobjekt ist, das ein modAttribut hat. Die __import__()Funktion muss also das Modulobjekt "ganz links" zurückgeben, damit es zugewiesen werden kann pkg. Diese beiden Importanweisungen bedeuten also:

pkg = __import__('pkg')
pkg = __import__('pkg.mod')

Im dritten, vierten und fünften Fall muss die importAnweisung mehr Arbeit leisten: Sie muss (möglicherweise) mehreren Namen zuweisen, die sie vom Modulobjekt erhalten muss. Die __import__()Funktion kann nur ein Objekt zurückgeben, und es gibt keinen wirklichen Grund, jeden dieser Namen aus dem Modulobjekt abzurufen (und dies würde die Implementierung viel komplizierter machen). Der einfache Ansatz wäre also ungefähr so ​​(für den dritten) Fall):

tmp = __import__('pkg')
mod = tmp.mod
mod2 = tmp.mod2

Dies funktioniert jedoch nicht, wenn pkges sich um ein Paket handelt und / mododer mod2um Module in diesem Paket , die nicht bereits importiert wurden , wie dies im dritten und fünften Fall der Fall ist. Die __import__()Funktion muss das wissen modund mod2sind Namen, auf die die importAnweisung zugreifen soll, damit sie sehen kann, ob es sich um Module handelt, und versucht, sie auch zu importieren. Der Anruf ist also näher an:

tmp = __import__('pkg', fromlist=['mod', 'mod2'])
mod = tmp.mod
mod2 = tmp.mod2

was bewirkt , dass __import__()und Last , um zu versuchen pkg.modund pkg.mod2sowie pkg(aber wenn mododer mod2nicht existieren, es ist nicht ein Fehler in dem __import__()Aufruf, einen Fehler erzeugen, das den blieb import. statement) Aber das ist noch nicht das Richtige für die vierten und fünftes Beispiel, denn wenn der Anruf so wäre:

tmp = __import__('pkg.mod', fromlist=['submod'])
submod = tmp.submod

Dann tmpwäre es pkgwie zuvor und nicht das pkg.modModul, von dem Sie das submodAttribut erhalten möchten . Die Implementierung hätte sich dafür entscheiden können, damit die importAnweisung zusätzliche Arbeit leistet, den Paketnamen aufteilt, .wie es die __import__()Funktion bereits tut, und die Namen durchläuft, aber dies hätte bedeutet, einen Teil des Aufwands zu duplizieren. Anstatt also, macht die Umsetzung __import__()Rückkehr die am weitesten rechts stehenden Modul anstelle des am weitesten links ein , wenn und nur wenn fromlist geben wird und nicht leeren.

(Die Syntax import pkg as pund from pkg import mod as mändert nichts an dieser Geschichte, außer den lokalen Namen, denen zugewiesen wird. Die __import__()Funktion sieht bei Verwendung nichts anderes asaus, alles bleibt in der importAnweisungsimplementierung.)

Thomas Wouters
quelle
5

Ich fühle mich immer noch komisch, wenn ich die Antwort lese, also habe ich die folgenden Codebeispiele ausprobiert.

Versuchen Sie zunächst, die folgende Dateistruktur zu erstellen:

tmpdir
  |A
     |__init__.py
     | B.py
     | C.py

Jetzt ist A a packageund Boder Cist a module. Wenn wir also einen Code wie diesen in ipython ausprobieren:

Führen Sie zweitens den Beispielcode in ipython aus:

  In [2]: kk = __import__('A',fromlist=['B'])

  In [3]: dir(kk)
  Out[3]: 
  ['B',
   '__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   '__path__']

Es scheint, als ob die fromlist wie erwartet funktioniert. Aber die Dinge werden verkabelt, wenn wir versuchen, die gleichen Dinge auf einem zu tun module. Angenommen, wir haben ein Modul namens C.py und Code darin:

  handlers = {}

  def hello():
      print "hello"

  test_list = []

Jetzt versuchen wir das Gleiche zu tun.

  In [1]: ls
  C.py

  In [2]: kk = __import__('C')

  In [3]: dir(kk)
  Out[3]: 
  ['__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   'handlers',
   'hello',
   'test_list']

Funktioniert es also, wenn wir nur die test_list importieren möchten?

  In [1]: kk = __import__('C',fromlist=['test_list'])

  In [2]: dir(kk)
  Out[2]: 
  ['__builtins__',
   '__doc__',
   '__file__',
   '__name__',
   '__package__',
   'handlers',
   'hello',
   'test_list']

Wie das Ergebnis zeigt, hilft der Parameter fromlist überhaupt nicht , wenn wir versuchen, fromlist für a moduleanstelle von a zu verwenden package, da moduleer kompiliert wurde. Sobald es importiert ist, gibt es keine Möglichkeit, die anderen zu ignorieren.

zhkzyth
quelle
2

Die Antwort finden Sie in der Dokumentation zu __import__:

Die fromlist sollte eine Liste der zu emulierenden Namen from name import ...oder eine leere Liste zum Emulieren sein import name.

Beachten Sie beim Importieren eines Moduls aus einem Paket, dass __import__('A.B', ...)Paket A zurückgegeben wird, wenn fromlist leer ist, aber das Submodul B, wenn fromlist nicht leer ist.

So funktioniert die Implementierung von __import__: Wenn Sie das Submodul möchten, übergeben Sie ein fromlistElement, das Sie aus dem Submodul importieren möchten, und die Implementierung, wenn __import__das Submodul zurückgegeben wird.

Weitere Erklärung

Ich denke, die Semantik existiert, so dass das relevanteste Modul zurückgegeben wird. Mit anderen Worten, ich habe ein Paket foomit einem Modul barmit Funktion baz. Wenn ich:

import foo.bar

Dann verweise ich auf bazals

foo.bar.baz()

Das ist wie __import__("foo.bar", fromlist=[]).

Wenn ich stattdessen importiere mit:

from foo import bar

Dann beziehe ich mich auf bazbar.baz ()

Welches wäre ähnlich wie __imoort__("foo.bar", fromlist=["something"]).

Wenn ich mache:

from foo.bar import baz

Dann verweise ich auf bazals

baz()

Welches ist wie __import__("foo.bar", fromlist=["baz"]).

Im ersten Fall müsste ich also den vollständig qualifizierten Namen verwenden und daher __import__den ersten Modulnamen zurückgeben, den Sie verwenden würden, um auf die importierten Elemente zu verweisen foo. Im letzten Fall barhandelt es sich um das spezifischste Modul, das die importierten Elemente enthält. Daher ist es sinnvoll, __import__das foo.barModul zurückzugeben.

Der zweite Fall ist etwas seltsam, aber ich vermute, er wurde so geschrieben, um den Import eines Moduls mithilfe der from <package> import <module>Syntax zu unterstützen, und ist in diesem Fall barimmer noch das spezifischste Modul, das zurückgegeben werden soll.

Mipadi
quelle
Zu sagen, "so funktioniert die Implementierung", beantwortet meine Frage nicht. Warum funktioniert das so? Das Formular "Emulieren des Namensimports ..." ist näher, aber unter welchen Umständen würden Sie das benötigen? Die fromlist macht keinen Unterschied, wie der Import tatsächlich funktioniert, daher sehe ich nicht, wo es einen Fall gibt, in dem Sie sie übergeben müssen, um etwas zu emulieren, außer was das offensichtliche Verhalten der Funktion sein sollte.
dh
1
Du hast recht, es wirft die Frage auf. Ich habe meine Antwort aktualisiert, um eine relevantere Antwort zu geben.
Mipadi