Подтвердить что ты не робот

Зависимости циклического модуля и относительный импорт в 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

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

Изменить. Этот вопрос касается не разработки программного обеспечения. Я знаю способы избежать круговой зависимости, но меня все равно интересует причина ошибки.

4b9b3361

Ответ 1

Сначала начнем с того, как 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        

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

Теперь, что IMPORT_FROM do?

посмотрим, что делает 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;

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

Теперь, что это связано с относительным импортом?

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

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

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

Для этого это поможет нам, если мы сможем увидеть, что делает python при импорте, так что наш счастливый день 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__._

hmm, что только что произошло до 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.

Теперь придет Вторая) часть, 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: Этот ответ был перефразирован, потому что, когда я пытался его прочитать, я заметил, как мой ответ был плохо сформулирован, надеюсь, теперь он будет более полезен:)

Ответ 2

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

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

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

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

Ответ 3

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

У меня был следующий модуль Structure:

base
 +guiStuff
   -gui
 +databaseStuff
   -db
 -basescript

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

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