Python中的循环导入依赖项
假设我有以下目录结构:Python中的循环导入依赖项,python,dependencies,circular-dependency,python-import,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包的\uuuu init\uuuu.py中,导入c包。但是c_file.py导入a.b.d 当c_file.py尝试导入a.b.d时,程序失败,表示b
a\
__init__.py
b\
__init__.py
c\
__init__.py
c_file.py
d\
__init__.py
d_file.py
在a
包的\uuuu init\uuuu.py
中,导入c
包。但是c_file.py
导入a.b.d
当c_file.py
尝试导入a.b.d
时,程序失败,表示b
不存在。(而且它真的不存在,因为我们正处于进口的过程中)
如何解决此问题?您可以推迟导入,例如在
a/\uuu init\uuuu.py
中:
def my_function():
from a.b.c import Blah
return Blah()
也就是说,将导入推迟到真正需要时。但是,我也会仔细查看我的包定义/使用,因为像前面指出的那样的循环依赖可能表明存在设计问题。如果a依赖于c,而c依赖于a,那么它们实际上不是同一个单元吗
您应该真正检查一下为什么将a和c拆分为两个包,因为要么您有一些代码应该拆分为另一个包(使它们都依赖于新包,而不是彼此),要么您应该将它们合并为一个包。另一个解决方案是使用d_文件的代理 例如,假设您希望与c_文件共享blah类。因此,d_文件包含:
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()
我想了好几次(通常是在处理需要相互了解的模型时)。简单的解决方案就是导入整个模块,然后引用您需要的东西 因此,与其这样做
from models import Student
在一个,和
from models import Classroom
另一方面,只要做
import models
在其中一个目录中,然后在需要时调用
models.school
。问题是,从目录运行时,默认情况下,只有作为子目录的包作为候选导入可见,因此您无法导入a.b.d。但是,您可以导入b.d.,因为b是a的子包
如果您真的想在c/\uuuu init\uuuuuuuuuuuuuuuuupy
中导入a.b.d,可以通过将系统路径更改为a上方的一个目录,并将a/\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu.py
中的导入更改为导入a.b.c来实现
您的a/\uuuuu init\uuuuu.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目录中的
\uuuu int\uuuuuuuuy.py
,将sys.path指向顶层目录,然后导入c中的任何模块中的\uuuuuu init\uuuuuuuuuuu
,以便能够使用完整路径导入a.b.d。我怀疑导入\uuuuuuuu init.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()
循环导入
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 _u; init _;方法中声明参数a的类型,但可以通过以下方式“强制转换”它:
def __init__(self, a):
self.a: playground.cyclic_import_a.A = a
类型提示导致的循环依赖关系
有了类型提示,就有更多机会创建循环导入。幸运的是,有一个使用特殊常量的解决方案:
下面的示例定义了顶点
类和边
类。一条边由两个顶点定义,而一个顶点维护它所属的相邻边的列表
如果没有类型提示,则不会出现错误
文件:vertex.py
文件:edge.py
类型提示导致导入错误
ImportError:无法从部分初始化的模块“Edge”导入名称“Edge”(很可能是由于循环导入)
文件:vertex.py
文件:edge.py
使用类型检查的解决方案
文件:vertex.py
文件:edge.py
带引号与不带引号的类型提示
在3.10之前的Python版本中,有条件导入的类型必须用引号括起来,使它们成为“前向引用”,这会在解释器运行时隐藏它们
在Python3.7、3.8和3.9中,一种解决方法是使用以下特殊导入
from __future__ import annotations
这允许将无引号的类型提示与条件导入结合使用
Python 3.10(请参阅)
在Python3.10中,函数和变量注释将不再适用
在定义时进行评估。相反,将保留字符串形式
在相应的注释中。静态类型检查器
在行为上看不到任何差异,而在
运行时将不得不执行延迟的评估
字符串形式是在编译步骤中从AST获得的,
这意味着字符串形式可能不会保留精确的
源的格式设置。注意:如果注释是字符串文字
现在,它仍将被包装在一个字符串中
也许你可以试试相对进口?这可能也有帮助,只是作为参考,在python 3.5(可能更高版本)上允许循环导入,但在3.4(可能更高版本)上不允许循环导入。如果捕获到导入错误,只要在第一个模块完成导入之前不需要在其他模块中使用任何内容,它就可以正常工作。可能是重复的,它们可以被视为同一个一揽子计划。但如果这导致了一个巨大的文件,那么它是不切实际的。我同意,循环依赖性通常意味着设计应该重新考虑。但是有一些设计模式是合适的(将文件合并在一起会产生一个巨大的文件)因此,我认为说应该组合软件包或重新评估设计是武断的。在不同的文件中修改全局模块属性会很快导致噩梦。有时循环引用确实是不可避免的。在这种情况下,这是唯一对我有效的方法。这不会在每次调用foo时增加很多开销吗?@Mr_and_Mrs_D-只是适度地。Python将所有导入的模块保存在一个全局缓存中(
sys.modules
),因此一个mod
class Edge:
def __init__(self, v1, v2):
self.v1 = v1
self.v2 = v2
from typing import List
from edge import Edge
class Vertex:
def __init__(self, label: str):
self.label = label
self.adjacency_list: List[Edge] = []
from vertex import Vertex
class Edge:
def __init__(self, v1: Vertex, v2: Vertex):
self.v1 = v1
self.v2 = v2
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'] = []
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
from __future__ import annotations