Зависимости циклического модуля и относительный импорт в Python - PullRequest
32 голосов
/ 15 июня 2011

Предположим, у нас есть два модуля с циклическими зависимостями:

# a.py
import b
def f(): return b.y
x = 42

# b.py
import a
def g(): return a.x
y = 43

Два модуля находятся в каталоге pkg с пустым __init__.py. Импорт pkg.a или pkg.b работает нормально, как описано в в этом ответе . Если я изменю импорт на относительный импорт

from . import b

Я получаю ImportError при попытке импортировать один из модулей:

>>> import pkg.a
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "pkg/a.py", line 1, in <module>
    from . import b
  File "pkg/b.py", line 1, in <module>
    from . import a
ImportError: cannot import name a

Почему я получаю эту ошибку? Разве ситуация не такая же, как и выше? (Это связано с этим вопросом ?)

Редактировать : Этот вопрос не о разработке программного обеспечения. Мне известны способы избежать циклической зависимости, но в любом случае меня интересует причина ошибки.

Ответы [ 3 ]

31 голосов
/ 17 июня 2011

Сначала давайте начнем с того, как from import работает в python:

Хорошо, давайте сначала посмотрим на байт-код:

>>> def foo():
...     from foo import bar

>>> dis.dis(foo)
2           0 LOAD_CONST               1 (-1)
              3 LOAD_CONST               2 (('bar',))
              6 IMPORT_NAME              0 (foo)
              9 IMPORT_FROM              1 (bar)
             12 STORE_FAST               0 (bar)
             15 POP_TOP             
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE        

хм, интересно :), поэтому from foo import bar переводится сначала IMPORT_NAME foo, что эквивалентно import foo, а затем IMPORT_FROM bar.

Что теперь IMPORT_FROM делает?

посмотрим, что сделает питон, когда найдет IMPORT_FROM:

TARGET(IMPORT_FROM)
     w = GETITEM(names, oparg);
     v = TOP();
     READ_TIMESTAMP(intr0);
     x = import_from(v, w);
     READ_TIMESTAMP(intr1);
     PUSH(x);
     if (x != NULL) DISPATCH();
     break;

Ну, в основном он получает имена для импорта, которые в нашей функции foo() будут bar, затем он выталкивает из стека фреймов значение v, которое является возвращением последнего выполненного кода операции IMPORT_NAME, затем вызовите функцию import_from() с двумя аргументами:

static PyObject *
import_from(PyObject *v, PyObject *name)
{
    PyObject *x;

    x = PyObject_GetAttr(v, name);

    if (x == NULL && PyErr_ExceptionMatches(PyExc_AttributeError)) {
        PyErr_Format(PyExc_ImportError, "cannot import name %S", name);
    }
    return x;
}

Как видите, функция import_from() довольно проста, сначала попытайтесь получить атрибут name из модуля v, если он не существует, он вызовет ImportError, иначе вернет этот атрибут.

Какое отношение это имеет к относительному импорту?

Хорошо относительный импорт, такой как from . import b, эквивалентен, например, в случае, который в вопросе OP, к from pkg import b.

Но как это случилось? Чтобы понять это, мы должны взглянуть на модуль import.c модуля python специально на функцию get_parent () . Как вы видите, функция довольно длинная, чтобы перечислять ее здесь, но в целом, когда она видит относительный импорт, она пытается заменить точку . родительским пакетом, в зависимости от модуля __main__, который снова из ОП вопрос это пакет pkg.

Теперь давайте соберем все это вместе и попытаемся выяснить, почему мы в конечном итоге получаем поведение в вопросе ОП.

В этом нам поможет, если мы увидим, что python делает при импорте, так что наш питон удачного дня уже имеет эту функцию, которую можно включить, запустив его в дополнительном подробном режиме -vv.

Итак, используя командную строку: python -vv -c 'import pkg.b':

Python 2.6.5 (r265:79063, Apr 16 2010, 13:57:41) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.

import pkg # directory pkg
# trying pkg/__init__.so
# trying pkg/__init__module.so
# trying pkg/__init__.py
# pkg/__init__.pyc matches pkg/__init__.py
import pkg # precompiled from pkg/__init__.pyc
# trying pkg/b.so
# trying pkg/bmodule.so
# trying pkg/b.py
# pkg/b.pyc matches pkg/b.py
import pkg.b # precompiled from pkg/b.pyc
# trying pkg/a.so
# trying pkg/amodule.so
# trying pkg/a.py
# pkg/a.pyc matches pkg/a.py
import pkg.a # precompiled from pkg/a.pyc
#   clear[2] __name__
#   clear[2] __file__
#   clear[2] __package__
#   clear[2] __name__
#   clear[2] __file__
#   clear[2] __package__
...
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "pkg/b.py", line 1, in <module>
    from . import a
  File "pkg/a.py", line 2, in <module>
    from . import a
ImportError: cannot import name a
# clear __builtin__._

хмм, что только что произошло до ImportError?

Первый) from . import a в pkg/b.py вызывается, что переводится, как объяснено выше, в from pkg import a, который снова в байт-коде эквивалентен import pkg; getattr(pkg, 'a'). Но подождите a это тоже модуль ?! Ну, вот и самое интересное, если у нас есть что-то вроде from module|package import module, в этом случае произойдет второй импорт, который является импортом модуля в предложении import. Итак, снова в примере с OP нам нужно импортировать pkg/a.py, и, как вы знаете, прежде всего, мы установили в нашем sys.modules ключ для нашего нового модуля, который будет pkg.a, а затем продолжим нашу интерпретацию модуля pkg/a.py, но перед тем, как модуль pkg/a.py закончит его импорт, вызовите from . import b.

Теперь перейдем к части Second) , pkg/b.py будет импортирован, и в свою очередь он сначала попытается import pkg, который, поскольку pkg уже импортирован, поэтому есть ключ pkg в нашем sys.modules он просто вернет значение этого ключа. Затем он import b установит pkg.b ключ в sys.modules и начнет интерпретацию. И мы приходим к этой линии from . import a!

Но помните, pkg/a.py уже импортировано, что означает ('pkg.a' in sys.modules) == True, поэтому импорт будет пропущен, и будет вызван только getattr(pkg, 'a'), но что произойдет? Python не завершил импорт pkg/a.py!? Таким образом, будет вызываться только getattr(pkg, 'a'), и это вызовет AttributeError в функции import_from(), которая будет переведена в ImportError(cannot import name a).

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ : Это мое собственное усилие, чтобы понять, что происходит внутри переводчика, я далеко не эксперт.

EDIt: Этот ответ был перефразирован, потому что, когда я попытался прочитать его снова, я заметил, как мой ответ был сформулирован неверно, надеюсь, теперь он будет более полезным :)

4 голосов
/ 15 июня 2011

(Кстати, относительный импорт не имеет значения. Использование from pkg import ... выявляет то же исключение.)

Я думаю, что здесь происходит то, что разница между from foo import bar и import foo.bar заключается в том, что в первом значение bar может быть модулем в pkg foo или переменной в модуле. foo. Во втором случае недопустимо, чтобы bar был чем-то иным, кроме модуля / пакета.

Это имело бы значение, потому что, если известно, что bar является модулем, содержимого sys.modules достаточно для его заполнения. Если это может быть переменная в модуле foo, то интерпретатор должен фактически просмотреть содержимое foo, но при импорте foo это будет недопустимым; Фактический модуль еще не был заполнен.

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

1 голос
/ 31 мая 2012

В качестве дополнительного примечания:

У меня был следующий модуль Структура:

base
 +guiStuff
   -gui
 +databaseStuff
   -db
 -basescript

Я хотел иметь возможность запустить мой сценарий на import base.basescript, однако это не удалось с ошибкой, поскольку файл gui имел import base.databaseStuff.db, что вызвало импорт base. Поскольку base был зарегистрирован только как __main__, это вызвало повторное выполнение всего импорта и вышеприведенную ошибку, если я не использовал внешний скрипт выше base, таким образом импортируя base / basescript только один раз. Чтобы предотвратить это, я добавил в свой базовый скрипт следующее:

if  __name__ == '__main__' or \
  not '__main__' in sys.modules or \
  sys.modules['__main__'].__file__ != __file__: 
    #imports here
...