关于依赖项:Python中的循环导入依赖项

Circular import dependency in Python

假设我有以下目录结构:

1
2
3
4
5
6
7
8
9
10
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

程序失败,说当c_file.py试图导入a.b.d时,b不存在。(它确实不存在,因为我们正在导入它。)

如何解决这个问题?


您可以推迟进口,例如在a/__init__.py中:

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

也就是说,把进口推迟到真正需要的时候。但是,我也会仔细查看我的包定义/使用,因为像所指出的那样的循环依赖可能表示一个设计问题。


如果a依赖于c,c依赖于a,那么它们不是同一个单位吗?

您应该真正检查为什么将A和C拆分为两个包,因为要么您有一些代码,要么您应该拆分为另一个包(以使它们都依赖于该新包,而不是彼此),要么您应该将它们合并到一个包中。


我想了好几次(通常是在处理需要相互了解的模型时)。简单的解决方案就是导入整个模块,然后引用您需要的东西。

所以不要这么做

1
from models import Student

一,和

1
from models import Classroom

在另一个,就这么做吧

1
import models

在其中一个,然后打电话给模特。需要的时候就叫教室。


问题是,从目录运行时,默认情况下只有子目录的包作为候选导入可见,因此不能导入a.b.d。但是,可以导入b.d,因为b是a的子包。

如果您真的想在c/__init__.py中导入a.b.d,您可以通过将系统路径更改为a上面的一个目录来完成,并将a/__init__.py中的导入更改为导入a.b.c。

您的a/__init__.py应该如下所示:

1
2
3
4
5
6
7
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不存在。您可以对c目录中的__int__.py进行黑客攻击,将sys.path指向顶级目录,然后在c中的任何模块中导入__init__,以便能够使用完整路径导入a.b.d。我怀疑导入__init__.py是一种好的做法,但它对我的用例有效。


另一种解决方案是使用d_文件的代理。

例如,假设您希望与C_文件共享blah类。因此,D_文件包含:

1
2
3
class blah:
    def __init__(self):
        print("blah")

以下是您在c_file.py中输入的内容:

1
2
3
4
5
6
7
# 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()

在A的init.py中:

1
2
3
4
5
6
7
8
9
10
11
12
13
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()