Циклические зависимости модулей и относительный импорт в Python

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

# 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 ответов


сначала давайте начнем с того, как 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 переводится на first IMPORT_NAME foo что эквивалентно import foo а то IMPORT_FROM bar.

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

давайте посмотрим, что делает python, когда он нашел 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;

Ну в основном он получает имена, чтобы импорт из, который находится в нашем будет 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 else верните это атрибут.

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

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

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

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

для этого это поможет нам, если мы сможем увидеть, что делает python при импорте, ну, это наш счастливый день python уже с этой функцией, которая может быть включена запустив его в режиме extra verbose -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'). Но подождите минутку!--33--> - это тоже модуль ?! Ну вот пришла забавная часть, если у нас есть что-то вроде from module|package import module in в этом случае произойдет второй импорт, который является импортом модуля в предложении import. Итак, снова в Примере OP нам нужно импортировать pkg/a.py, и, как вы знаете, прежде всего мы установили в нашем sys.modules ключ для нашего нового модуля, который будет pkg.a и затем мы продолжаем нашу интерпретацию модуля pkg/a.py, но перед модулем pkg/a.py завершить импорт вызова from . import b.

наступила секунду) часть 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') будет называться , но что будет дальше ? питон не закончил импорт pkg/a.py !? Так только getattr(pkg, 'a') будет вызван, и это поднимет AttributeError на import_from() функция, которая будет переведена на ImportError(cannot import name a).

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

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


(Incedentally, относительный импорт не имеет значения. Используя 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


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

у меня была следующая структура модуля:

base
 +guiStuff
   -gui
 +databaseStuff
   -db
 -basescript

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

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