Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/python/356.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python中的循环导入依赖项_Python_Dependencies_Circular Dependency_Python Import - Fatal编程技术网

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