Python中的循环导入依赖项

问题描述 投票:66回答:6

假设我有以下目录结构:

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不存在。 (它确实不存在,因为我们正在进口它。)

如何解决这个问题呢?

python dependencies circular-dependency python-import
6个回答
58
投票

如果一个取决于c和c取决于a,那么它们实际上不是同一个单位吗?

你应该仔细研究为什么你将a和c分成两个包,因为要么你有一些代码你应该拆分成另一个包(使它们都依赖于那个新的包,而不是彼此),或者你应该合并它们成一个包。


148
投票

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

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

也就是说,将导入推迟到真正需要之前。但是,我也会仔细查看我的包定义/用法,因为像指出的那样循环依赖可能表示设计问题。


24
投票

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

所以不要这样做

from models import Student

在一个,和

from models import Classroom

在另一方面,就这样做

import models

在其中一个,然后调用models.Classroom当你需要它。


0
投票

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

如果你真的想在c/__init__.py中导入a.b.d,可以通过将系统路径更改为a之上的一个目录并将a/__init__.py中的导入更改为import 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不存在。您可以破解c目录中的__int__.py,将sys.path指向顶级目录,然后在c内的任何模块中导入__init__,以便能够使用完整路径导入a.b.d.我怀疑导入__init__.py是一个好习惯,但它对我的用例有效。


0
投票

我建议采用以下模式。使用它将允许自动完成和类型提示正常工作。

cyclic_import_啊.朋友

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_不.朋友

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

你不能在类B __ init __方法中声明参数a的类型,但你可以这样“强制转换”它:

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

-3
投票

另一种解决方案是使用d_file的代理。

例如,假设您要与c_file共享blah类。因此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() 
© www.soinside.com 2019 - 2024. All rights reserved.