Циркулярная зависимость импорта в 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()