Циркулярная зависимость импорта в Python
Скажем, у меня есть следующая структура каталогов:
a\
__init__.py
b\
__init__.py
c\
__init__.py
c_file.py
d\
__init__.py
d_file.py
В пакете a
__init__.py
пакет c
импортируется. Но c_file.py
импортирует a.b.d
.
Программа не работает, говоря b
не существует, когда c_file.py
пытается импортировать a.b.d
. (И это действительно не существует, потому что мы были в середине импорта его.)
Как эта проблема может быть устранена?
Ответы
Ответ 1
Если a зависит от c и c зависит от a, они не являются фактически одной и той же единицей, то?
Вы должны действительно изучить, почему вы разделили a и c на два пакета, потому что либо у вас есть код, который вы должны разделить на другой пакет (чтобы они зависели от этого нового пакета, но не друг от друга), или вы должны объединить их в один пакет.
Ответ 2
Вы можете отложить импорт, например, в a/__init__.py
:
def my_function():
from a.b.c import Blah
return Blah()
то есть отложить импорт до тех пор, пока он действительно не понадобится. Тем не менее, я бы также внимательно рассмотрел определения/использование пакетов, поскольку циклическая зависимость, такая как указанная, может указывать на проблему проектирования.
Ответ 3
Я задавался этим вопросом пару раз (обычно, имея дело с моделями, которые должны знать друг о друге). Простое решение - просто импортировать весь модуль, а затем ссылаться на то, что вам нужно.
Итак, вместо того, чтобы делать
from models import Student
в одном и
from models import Classroom
в другом, просто
import models
в одном из них, затем вызовите модели. Классы, когда вам это нужно.
Ответ 4
Проблема заключается в том, что при запуске из каталога по умолчанию в качестве импорта кандидата отображаются только пакеты, которые являются подкаталогами, поэтому вы не можете импортировать файл a.b.d. Однако вы можете импортировать b.d. так как b является дополнительным пакетом.
Если вы действительно хотите импортировать a.b.d в c/__init__.py
, вы можете это сделать, изменив системный путь на один каталог выше a и изменив импорт в a/__init__.py
для импорта a.b.c.
Ваш a/__init__.py
должен выглядеть следующим образом:
import sys
import os
# set sytem path to be directory above so that a can be a
# package namespace
DIRECTORY_SCRIPT = os.path.dirname(os.path.realpath(__file__))
sys.path.insert(0,DIRECTORY_SCRIPT+"/..")
import a.b.c
Дополнительная трудность возникает, когда вы хотите запускать модули в c как скрипты. Здесь пакеты a и b не существуют. Вы можете взломать __int__.py
в каталоге c, чтобы указать sys.path в каталог верхнего уровня, а затем импортировать __init__
в любые модули внутри c, чтобы иметь возможность использовать полный путь для импорта a.b.d. Я сомневаюсь, что это хорошая практика для импорта __init__.py
, но это сработало для моих случаев использования.
Ответ 5
Другое решение - использовать прокси для d_file.
Например, скажем, что вы хотите разделить класс blah с c_file. Таким образом, d_file содержит:
class blah:
def __init__(self):
print("blah")
Вот что вы вводите в c_file.py:
# do not import the d_file !
# instead, use a place holder for the proxy of d_file
# it will be set by a __init__.py after imports are done
d_file = None
def c_blah(): # a function that calls d_file blah
d_file.blah()
И в init.py:
from b.c import c_file
from b.d import d_file
class Proxy(object): # module proxy
pass
d_file_proxy = Proxy()
# now you need to explicitly list the class(es) exposed by d_file
d_file_proxy.blah = d_file.blah
# finally, share the proxy with c_file
c_file.d_file = d_file_proxy
# c_file is now able to call d_file.blah
c_file.c_blah()