Чому __import__ Python вимагає fromlist?


76

У Python, якщо ви хочете програмно імпортувати модуль, ви можете зробити:

module = __import__('module_name')

Якщо ви хочете імпортувати підмодуль, ви думаєте, що це буде простою справою:

module = __import__('module_name.submodule')

Звичайно, це не працює; ви просто отримуєте module_nameзнову. Ви повинні зробити:

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

Чому? Фактичне значення fromlist, здається, не має значення, якщо воно не порожнє. Який сенс вимагати аргументу, а потім ігнорувати його значення?

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

Відповіді:


127

Насправді, поведінка в __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оператор повинен призначити «найлівіший» об'єкт модуля до « крайнього лівого» імені: 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__()вже робить функція, та обходячи імена, але це означало б дублювання деяких зусиль. Таким чином, замість того , реалізація MADE __import__()повернути праву більшість модуля замість самого лівих один , якщо і тільки якщо fromlist передається і не спустошити.

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


5

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

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

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

Тепер A є a package, Bабо Cє a module. Отже, коли ми пробуємо код, подібний до цього в ipython:

По-друге, запустіть зразок коду в ipython:

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

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

Здається, список із списку працює так, як ми очікували. Але все стає дротовим, коли ми намагаємось робити те саме на a 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замість a package, параметр fromlist зовсім не допомагає, оскільки moduleбув скомпільований. Після імпортування неможливо ігнорувати інші.


2

Відповідь можна знайти в документації щодо __import__:

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

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

Отже, в основному саме так працює реалізація __import__: якщо ви хочете підмодуль, ви передаєте fromlistвміст, який ви хочете імпортувати з підмодуля, а реалізація if __import__така, що підмодуль повертається.

Подальше пояснення

Я думаю, що семантика існує так, що повертається найбільш відповідний модуль. Іншими словами, скажімо, у мене є пакет, fooщо містить модуль barз функцією baz. Якщо я:

import foo.bar

Тоді я маю на увазі , bazяк

foo.bar.baz()

Це як __import__("foo.bar", fromlist=[]).

Якщо замість цього я імпортую за допомогою:

from foo import bar

Тоді я називаю bazbar.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це все ще найбільш конкретний модуль для повернення.


Вимовляння "саме так працює впровадження" не відповідає на моє запитання. Чому це працює так? Сказати форму "наслідувати імпорту з імені ..." ближче, але за яких обставин це вам потрібно? Fromlist не має жодної різниці в тому, як насправді працює імпорт , тому я не бачу, де є випадок, коли вам потрібно було б передати його для емуляції чого-небудь, крім того, яка повинна бути очевидна поведінка функції.
ieure

1
Ви маєте рацію, це напрошується питання. Я оновив свою відповідь, щоб дати більш відповідну відповідь.
mipadi
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.