web-dev-qa-db-fra.com

Pourquoi __import__ de Python nécessite-t-il une liste?

En Python, si vous souhaitez importer par programme un module, vous pouvez faire:

module = __import__('module_name')

Si vous souhaitez importer un sous-module, vous penseriez que ce serait une simple question de:

module = __import__('module_name.submodule')

Bien sûr, cela ne fonctionne pas; vous obtenez juste module_name encore. Tu dois faire:

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

Pourquoi? La valeur réelle de fromlist ne semble pas avoir d'importance du tout, tant qu'elle n'est pas vide. Quel est l'intérêt d'exiger un argument, puis d'ignorer ses valeurs?

La plupart des trucs de Python semble être fait pour une bonne raison, mais pour la vie de moi, je ne peux pas trouver d'explication raisonnable pour que ce comportement existe.

74
ieure

En fait, le comportement de __import__() est entièrement dû à l'implémentation de l'instruction import, qui appelle __import__(). Il existe fondamentalement cinq façons légèrement différentes __import__() peuvent être appelées par import (avec deux catégories principales):

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

Dans le premier et deuxième cas, l'instruction import doit affecter l'objet module "le plus à gauche" au "plus à gauche" "nom: pkg. Après import pkg.mod vous pouvez faire pkg.mod.func() car l'instruction import a introduit le nom local pkg, qui est un objet module qui a un mod attribut. Ainsi, la fonction __import__() doit renvoyer l'objet module "le plus à gauche" afin qu'il puisse être affecté à pkg. Ces deux déclarations d'importation se traduisent donc par:

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

Dans les troisième, quatrième et cinquième cas, l'instruction import doit faire plus de travail: elle doit attribuer à (potentiellement) plusieurs noms, qu'elle doit obtenir de l'objet module. La fonction __import__() ne peut renvoyer qu'un seul objet, et il n'y a aucune raison réelle de lui faire récupérer chacun de ces noms à partir de l'objet module (et cela rendrait l'implémentation beaucoup plus compliquée.) Donc, l'approche simple serait quelque chose comme (pour le troisième cas):

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

Cependant, cela ne fonctionnera pas si pkg est un package et mod ou mod2 sont des modules de ce package qui ne sont pas déjà importés , comme ils le sont dans les troisième et cinquième cas. La fonction __import__() doit savoir que mod et mod2 sont des noms que l'instruction import voudra avoir accessibles, afin qu'elle puisse voir s'ils sont des modules et essayez de les importer aussi. L'appel est donc plus proche de:

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

ce qui fait que __import__() essaie de charger pkg.mod et pkg.mod2 ainsi que pkg (mais si mod ou mod2 n'existe pas, ce n'est pas une erreur dans l'appel __import__(); produire une erreur est laissé à l'instruction import.) Mais ce n'est toujours pas la bonne chose pour la quatrième et cinquième exemple, parce que si l'appel était ainsi:

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

alors tmp finirait par être pkg, comme précédemment, et non le module pkg.mod dont vous souhaitez obtenir l'attribut submod. L'implémentation aurait pu décider de faire en sorte que l'instruction import fasse un travail supplémentaire, en divisant le nom du package sur . comme la fonction __import__() le fait déjà et en traversant les noms, mais cela aurait signifié la duplication d'une partie de l'effort. Donc, à la place, l'implémentation fait que __import__() renvoie le module le plus à droite au lieu du gauche -most one si et seulement si fromlist est passé et non vide.

(La syntaxe import pkg as p et from pkg import mod as m ne change rien à cette histoire, sauf aux noms locaux qui sont attribués - la fonction __import__() ne voit rien de différent lorsque as est utilisé, tout reste dans l'implémentation de l'instruction import.)

123
Thomas Wouters

Je me sens toujours bizarre quand j'ai lu la réponse, j'ai donc essayé les exemples de code ci-dessous.

Tout d'abord, essayez de construire la structure de fichier ci-dessous:

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

Maintenant, A est un package et B ou C est un module. Donc, quand nous essayons du code comme ceux-ci en ipython:

Ensuite, exécutez l'exemple de code dans ipython:

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

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

Il semble que la liste fonctionne comme prévu. Mais les choses deviennent câblées lorsque nous essayons de faire les mêmes choses sur un module. Supposons que nous ayons un module appelé C.py et du code dedans:

  handlers = {}

  def hello():
      print "hello"

  test_list = []

Alors maintenant, nous essayons de faire la même chose là-dessus.

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

Donc, quand nous voulons juste importer la liste de tests, ça marche?

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

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

Comme le montre le résultat, lorsque nous essayons d'utiliser fromlist sur un module plutôt qu'un package, le paramètre fromlist n'aide pas du tout car module a été compilé. Une fois importé, il n'y a aucun moyen d'ignorer les autres.

5
zhkzyth

La réponse se trouve dans la documentation de __import__:

La liste de provenance doit être une liste de noms à émuler from name import ... Ou une liste vide à émuler import name.

Lors de l'importation d'un module à partir d'un package, notez que __import__('A.B', ...) renvoie le package A lorsque fromlist est vide, mais son sous-module B lorsque fromlist n'est pas vide.

Donc, en gros, c'est comme ça que l'implémentation de __import__ Fonctionne: si vous voulez le sous-module, vous passez un fromlist contenant quelque chose que vous voulez importer du sous-module, et l'implémentation si __import__ est tel que le sous-module est retourné.

Plus d'explications

Je pense que la sémantique existe pour que le module le plus pertinent soit retourné. En d'autres termes, disons que j'ai un package foo contenant le module bar avec la fonction baz. Si je:

import foo.bar

Ensuite, je me réfère à baz comme

foo.bar.baz()

C'est comme __import__("foo.bar", fromlist=[]).

Si à la place j'importe avec:

from foo import bar

Ensuite, je me réfère à baz comme bar.baz ()

Ce qui serait similaire à __imoort__("foo.bar", fromlist=["something"]).

Si je fais:

from foo.bar import baz

Ensuite, je me réfère à baz comme

baz()

Qui est comme __import__("foo.bar", fromlist=["baz"]).

Donc, dans le premier cas, je devrais utiliser le nom complet, donc __import__ Renvoie le premier nom de module que vous utiliseriez pour faire référence aux éléments importés, à savoir foo. Dans le dernier cas, bar est le module le plus spécifique contenant les éléments importés, il est donc logique que __import__ Renvoie le module foo.bar.

Le deuxième cas est un peu bizarre, mais je suppose qu'il a été écrit de cette façon pour prendre en charge l'importation d'un module en utilisant la syntaxe from <package> import <module>, Et dans ce cas, bar est toujours le module le plus spécifique à renvoyer. .

2
mipadi