Почему Python __import__ требует fromlist?

В Python, если вы хотите программно импортировать модуль, вы можете сделать:

module = __import__('module_name')

Если вы хотите импортировать подмодуль, вы бы подумали, что это будет простой вопрос:

module = __import__('module_name.submodule')

Конечно, это не работает; вы снова получите module_name. Вы должны сделать:

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

Почему? Фактическое значение fromlist, похоже, не имеет значения, если оно не пустое. Какой смысл требовать аргумент, затем игнорируя его значения?

Большинство вещей в Python, кажется, сделано по уважительной причине, но для моей жизни я не могу придумать разумного объяснения этого поведения.

Ответ 1

На самом деле поведение __import__() происходит целиком из-за реализации инструкции import, которая вызывает __import__(). В основном пять различных способов __import__() могут быть вызваны import (с двумя основными категориями):

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

В первом и втором случаях оператор import должен назначить объекту модуля "самый левый" имя "left-most": pkg. После import pkg.mod вы можете сделать pkg.mod.func(), потому что оператор import ввел локальное имя pkg, которое является объектом модуля с атрибутом mod. Таким образом, функция __import__() должна возвращать объект модуля "самый левый", поэтому его можно назначить на pkg. Таким образом, эти два оператора импорта преобразуются в:

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

В третьем, четвертом и пятом случае оператор import должен выполнять больше работы: он должен назначить (потенциально) несколько имен, которые он должен получить от объекта модуля. Функция __import__() может возвращать только один объект, и нет никакой реальной причины, чтобы заставить это получить каждое из этих имен из объекта модуля (и это сделало бы реализацию намного сложнее.) Таким образом, простой подход был бы чем-то вроде ( для третьего случая):

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

Однако это не сработает, если pkg является пакетом, а mod или mod2 являются модулями этого пакета, которые еще не импортированы, поскольку они находятся в третьем и пятом случае. Функция __import__() должна знать, что mod и mod2 являются именами, которые оператор import должен иметь доступный, чтобы он мог видеть, являются ли они модулями и пытаются их импортировать. Таким образом, вызов ближе к:

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

который вызывает __import__(), чтобы попытаться загрузить pkg.mod и pkg.mod2, а также pkg (но если mod или mod2 не существует, это не ошибка в вызове __import__(), создавая ошибку, остается инструкция import). Но это все еще не подходит для четвертого и пятого примеров, потому что если вызов был таким:

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

тогда tmp окажется pkg, как и раньше, а не модулем pkg.mod, из которого вы хотите получить атрибут submod. Реализация могла бы решить сделать так, чтобы оператор import выполнял дополнительную работу, разбивая имя пакета на ., как функция __import__(), уже выполняет и перемещая имена, но это означало бы дублирование некоторых усилий. Таким образом, вместо этого реализация __import__() возвращает самый правый модуль, а не самый левый , если и только если из списка передан, а не пуст.

(Синтаксис import pkg as p и from pkg import mod as m ничего не меняет об этой истории, за исключением того, какие локальные имена назначаются - функция __import__() не видит ничего другого, когда используется as, все это остается в import.)

Ответ 2

Я все еще чувствую себя странно, когда читаю ответ, поэтому попробовал приведенные ниже примеры кода.

Сначала попробуйте построить ниже структуру файла:

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

Теперь A является package, а B или C является module. Поэтому, когда мы пытаемся использовать такой код в ipython:

Во-вторых, запустите пример кода в ipython:

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

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

Кажется, что fromlist работает так, как мы ожидали. Но вещи становятся проводными, когда мы пытаемся сделать то же самое на module. Предположим, что в нем имеется модуль C.py и код:

  handlers = {}

  def hello():
      print "hello"

  test_list = []

Итак, теперь мы пытаемся сделать то же самое на нем.

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

Итак, когда мы просто хотим импортировать test_list, это работает?

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

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

Как показывает результат, когда мы пытаемся использовать fromlist в module, а не в package, параметр fromlist не помогает вообще, потому что module был скомпилирован. Когда он импортируется, нет возможности игнорировать другие.

Ответ 3

Ответ можно найти в документации для __import__:

Список из списка должен быть списком имен для эмуляции from name import ... или пустым списком для эмулирования import name.

При импорте модуля из пакета обратите внимание, что __import__('A.B', ...) возвращает пакет A, когда fromlist пуст, но его подмодуль B, когда fromlist не пуст.

Итак, в основном, как работает реализация __import__: если вы хотите подмодуль, вы передаете fromlist, содержащий что-то, что вы хотите импортировать из подмодуля, а реализация if __import__ такова, что подмодуль возвращается.

Дальнейшее объяснение

Я думаю, что семантика существует, так что возвращается наиболее релевантный модуль. Другими словами, скажем, у меня есть пакет foo, содержащий модуль bar с функцией baz. Если I:

import foo.bar

Затем я называю baz как

foo.bar.baz()

Это похоже на __import__("foo.bar", fromlist=[]).

Если вместо этого я импортирую с помощью:

from foo import bar

Затем я называю baz как   bar.baz()

Что было бы похоже на __imoort__("foo.bar", fromlist=["something"]).

Если я это сделаю:

from foo.bar import baz

Затем я называю baz как

baz()

Это как __import__("foo.bar", fromlist=["baz"]).

Итак, в первом случае мне нужно будет использовать полностью квалифицированное имя, поэтому __import__ возвращает имя первого модуля, которое вы будете использовать для ссылки на импортированные элементы: foo. В последнем случае bar - это наиболее специфический модуль, содержащий импортированные элементы, поэтому имеет смысл, что __import__ вернет модуль foo.bar.

Второй случай немного странный, но я предполагаю, что он был написан таким образом, чтобы поддерживать импорт модуля с использованием синтаксиса from <package> import <module>, и в этом случае bar по-прежнему является наиболее конкретным модулем для возврата.