繁体   English   中英

Python中的循环导入依赖

[英]Circular import dependency in 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导入abd

程序失败,说当c_file.py尝试导入abdb不存在。 (而且它真的不存在,因为我们正在导入它。)

如何解决这个问题?

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

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

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

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

您应该真正检查为什么将 a 和 c 拆分为两个包,因为要么您有一些代码,您应该将其拆分到另一个包中(使它们都依赖于那个新包,而不是相互依赖),或者您应该合并它们成一包。

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

所以而不是做

from models import Student

在一个,和

from models import Classroom

另一方面,就做

import models

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

类型提示导致的循环依赖

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

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

没有类型提示,没有错误

文件:顶点.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

类型提示导致导入错误

导入错误:无法从部分初始化的模块“边缘”导入名称“边缘”(很可能是由于循环导入)

文件:顶点.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 的解决方案

文件:顶点.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

带引号与不带引号的类型提示

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

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

from __future__ import annotations

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

Python 3.10(参见PEP 563 - 注释的推迟评估

在 Python 3.10 中,将不再在定义时评估函数和变量注释。 相反,字符串形式将保留在相应的注释字典中。 静态类型检查器在行为上没有区别,而在运行时使用注释的工具将不得不执行延迟评估。

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

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

如果你真的想在c/__init__.py导入 abd 你可以通过将系统路径更改为a/__init__.py上方的一个目录并将a/__init__.py的导入更改为 import abc 来实现

你的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__以便能够使用完整路径导入 abd 我怀疑这是一个好习惯导入__init__.py但它适用于我的用例。

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

循环导入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()

循环导入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

另一种解决方案是为 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()

在 a 的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() 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM