Круговая зависимость импорта в Python - PullRequest
66 голосов
/ 12 октября 2009

Допустим, у меня есть следующая структура каталогов:

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

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

Ответы [ 6 ]

148 голосов
/ 12 октября 2009

Вы можете отложить импорт, например, в a/__init__.py:

def my_function():
    from a.b.c import Blah
    return Blah()

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

58 голосов
/ 12 октября 2009

Если a зависит от c, а c зависит от a, не являются ли они фактически одной и той же единицей?

Вы должны действительно выяснить, почему вы разделили a и c на два пакета, потому что либо у вас есть некоторый код, который вы должны разделить на другой пакет (чтобы они оба зависели от этого нового пакета, но не друг от друга), или вы следует объединить их в одну упаковку.

24 голосов
/ 04 октября 2013

Я задавался вопросом об этом пару раз (обычно, имея дело с моделями, которые должны знать друг о друге). Простое решение - просто импортировать весь модуль, а затем ссылаться на то, что вам нужно.

Так что вместо того, чтобы

from models import Student

в одном и

from models import Classroom

в другом, просто сделай

import models

в одном из них, затем вызовите модели. Класс, когда вам это нужно.

0 голосов
/ 25 апреля 2019

Предлагаю следующую схему. С его помощью автозаполнение и подсказки будут работать правильно.

cyclic_import_a.py

import playground.cyclic_import_b

class A(object):
    def __init__(self):
        pass

    def print_a(self):
        print('a')

if __name__ == '__main__':
    a = A()
    a.print_a()

    b = playground.cyclic_import_b.B(a)
    b.print_b()

cyclic_import_b.py

import playground.cyclic_import_a

class B(object):
    def __init__(self, a):
        self.a: playground.cyclic_import_a.A = a

    def print_b(self):
        print('b1-----------------')
        self.a.print_a()
        print('b2-----------------')

Вы не можете импортировать классы A & B, используя этот синтаксис

from playgroud.cyclic_import_a import A
from playground.cyclic_import_b import B

Вы не можете объявить тип параметра a в методе класса __ init __, но вы можете "привести его" следующим образом:

def __init__(self, a):
    self.a: playground.cyclic_import_a.A = a
0 голосов
/ 31 августа 2015

Проблема заключается в том, что при запуске из каталога по умолчанию только пакеты, являющиеся подкаталогами, отображаются в качестве кандидатов на импорт, поэтому вы не можете импортировать a.b.d. Однако вы можете импортировать b.d. так как b является подпакетом a.

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

0 голосов
/ 17 июля 2012

Другое решение - использовать прокси для 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's __init__.py after imports are done
d_file = None 

def c_blah(): # a function that calls d_file's 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() 
...