Python中的循环导入依赖项

时间:2009-10-12 19:19:31

标签: python dependencies circular-dependency python-import

假设我有以下目录结构:

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

如何解决这个问题?

7 个答案:

答案 0 :(得分:147)

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

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

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

答案 1 :(得分:58)

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

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

答案 2 :(得分:24)

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

所以不要做

from models import Student

在一个,

from models import Classroom

在另一方面,只需做

import models

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

答案 3 :(得分:3)

由于类型提示引起的循环依赖

有了类型提示,创建循环导入的机会就更多了。幸运的是,有一个使用特殊常量的解决方案:typing.TYPE_CHECKING

下面的示例定义一个Vertex类和一个Edge类。一条边由两个顶点定义,一个顶点维护着它所属的相邻边的列表。

没有类型提示,没有错误

文件:vertex.py

class Vertex:
    def __init__(self, label):
        self.label = label
        self.adjacency_list = []

文件:edge.py

class Edge:
    def __init__(self, v1, v2):
        self.v1 = v1
        self.v2 = v2

类型提示导致ImportError

ImportError:无法从部分初始化的模块“ edge”中导入名称“ Edge”(很可能是由于循环导入)

文件:vertex.py

from typing import List
from edge import Edge


class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List[Edge] = []

文件:edge.py

from vertex import Vertex


class Edge:
    def __init__(self, v1: Vertex, v2: Vertex):
        self.v1 = v1
        self.v2 = v2

使用TYPE_CHECKING的解决方案

文件:vertex.py

from typing import List, TYPE_CHECKING

if TYPE_CHECKING:
    from edge import Edge


class Vertex:
    def __init__(self, label: str):
        self.label = label
        self.adjacency_list: List['Edge'] = []

文件:edge.py

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from vertex import Vertex


class Edge:
    def __init__(self, v1: 'Vertex', v2: 'Vertex'):
        self.v1 = v1
        self.v2 = v2

引用类型引用与未引用类型提示

在3.10之前的Python版本中,必须将有条件导入的类型括在引号中,使它们成为“正向引用”,从而将它们隐藏在解释器运行时中。

在Python 3.7、3.8和3.9中,一种解决方法是使用以下特殊导入。

from __future__ import annotations

这允许结合使用不带引号的类型提示和条件导入。

Python 3.10(请参见PEP 563 -- Postponed Evaluation of Annotations

在Python 3.10中,函数和变量注释将不再是 在定义时间进行评估。相反,将保留字符串形式 在相应的注释词典中。静态类型检查器 不会看到行为上的差异,而在 运行时将不得不执行推迟的评估。

字符串形式是在编译步骤中从AST获得的, 这意味着字符串形式可能不会保留确切的字符串 源格式。注意:如果注释是字符串文字 已经,它仍然会包裹在字符串中。

答案 4 :(得分:0)

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

如果你真的想在$arr1 = [array('nombre'=>'Pilsener'),array('nombre'=>'Golden'),array('nombre'=>'Suprema')]; $arr2 = [6,5,1]; $new_array = []; $i=0; foreach($arr1 as $arr) { $temp_arr['nombre'] = $arr['nombre']; $arr['cantidad'] = $arr2[$i]; $new_array[] = $arr; $i++; } 中导入a.b.d,可以通过将系统路径更改为a之上的一个目录并将c/__init__.py中的导入更改为导入a.b.c来实现此目的。

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

a/__init__.py

如果要将c中的模块作为脚本运行,则会出现其他问题。包a和b不存在。您可以破解c目录中的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 以将sys.path指向顶级目录,然后在c中的任何模块中导入__int__.py,以便能够使用完整路径导入a.b.d.我怀疑导入__init__是一种好习惯,但它对我的用例有效。

答案 5 :(得分:0)

我建议采用以下模式。使用它可以使自动完成和键入提示正常工作。

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

您不能在类B __ init __方法中声明参数a的类型,但是可以通过以下方式“投射”它:

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

答案 6 :(得分:-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()