Python 中的循环导入依赖

发布于 2024-08-07 17:44:52 字数 447 浏览 7 评论 0原文

假设我有以下目录结构:

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 尝试导入 abd 时,程序失败,提示 b 不存在。 (它确实不存在,因为我们正在导入它。)

如何解决这个问题?

Let's say I have the following directory structure:

a\
    __init__.py
    b\
        __init__.py
        c\
            __init__.py
            c_file.py
        d\
            __init__.py
            d_file.py

In the a package's __init__.py, the c package is imported. But c_file.py imports a.b.d.

The program fails, saying b doesn't exist when c_file.py tries to import a.b.d. (And it really doesn't exist, because we were in the middle of importing it.)

How can this problem be remedied?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

半城柳色半声笛 2024-08-14 17:44:52

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

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

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

You may defer the import, for example in a/__init__.py:

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

that is, defer the import until it is really needed. However, I would also have a close look at my package definitions/uses, as a cyclic dependency like the one pointed out might indicate a design problem.

九公里浅绿 2024-08-14 17:44:52

如果a依赖于c并且c依赖于a,那么它们实际上不是同一个单位吗?

您应该真正检查为什么将 a 和 c 拆分为两个包,因为您应该将一些代码拆分到另一个包中(以使它们都依赖于该新包,但不依赖于彼此),或者您应该合并它们装入一个包中。

If a depends on c and c depends on a, aren't they actually the same unit then?

You should really examine why you have split a and c into two packages, because either you have some code you should split off into another package (to make them both depend on that new package, but not each other), or you should merge them into one package.

冷心人i 2024-08-14 17:44:52

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

因此,不要

from models import Student

在一个和

from models import Classroom

另一个中执行,只需

import models

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

I've wondered this a couple times (usually while dealing with models that need to know about each other). The simple solution is just to import the whole module, then reference the thing that you need.

So instead of doing

from models import Student

in one, and

from models import Classroom

in the other, just do

import models

in one of them, then call models.Classroom when you need it.

醉城メ夜风 2024-08-14 17:44:52

由于类型提示而产生的循环依赖 通过

类型提示,有更多机会创建循环导入。幸运的是,有一个使用特殊常量的解决方案:打字。 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

导入错误:无法从部分初始化的模块“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

请注意缺少类型提示周围的引号。由于 Python 3.10 中的注释评估被推迟,这些类型提示是就好像它们在引号中一样对待。

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

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

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

from __future__ import annotations

Circular Dependencies due to Type Hints

With type hints, there are more opportunities for creating circular imports. Fortunately, there is a solution using the special constant: typing.TYPE_CHECKING.

The following example defines a Vertex class and an Edge class. An edge is defined by two vertices and a vertex maintains a list of the adjacent edges to which it belongs.

Without Type Hints, No Error

File: vertex.py

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

File: edge.py

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

Type Hints Cause ImportError

ImportError: cannot import name 'Edge' from partially initialized module 'edge' (most likely due to a circular import)

File: 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] = []

File: edge.py

from vertex import Vertex


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

Solution using TYPE_CHECKING

File: 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] = []

File: 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

Notice the absence of the quotes around the type hints. Because of the postponed evaluation of annotations in Python 3.10, these type hints are treated as if they were in quotes.

Quoted vs. Unquoted Type Hints

In versions of Python prior to 3.10, conditionally imported types must be enclosed in quotes, making them “forward references”, which hides them from the interpreter runtime.

In Python 3.7, 3.8, and 3.9, a workaround is to use the following special import.

from __future__ import annotations
爱人如己 2024-08-14 17:44:52

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

如果您确实想在 c/__init__.py 中导入 abd,可以通过将系统路径更改为 a 上面的一个目录并更改 a/__init__.py 中的导入来实现此目的code> 为 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 是个好习惯,但它对我的用例有效。

The problem is that when running from a directory, by default only the packages that are sub directories are visible as candidate imports, so you cannot import a.b.d. You can however import b.d. since b is a sub package of a.

If you really want to import a.b.d in c/__init__.py you can accomplish this by changing the system path to be one directory above a and change the import in a/__init__.py to be import a.b.c.

Your a/__init__.py should look like this:

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

An additional difficulty arises when you want to run modules in c as scripts. Here the packages a and b do not exist. You can hack the __int__.py in the c directory to point the sys.path to the top-level directory and then import __init__ in any modules inside c to be able to use the full path to import a.b.d. I doubt that it is good practice to import __init__.py but it has worked for my use cases.

扮仙女 2024-08-14 17:44:52

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

cyclo_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()

cyclo_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

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

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

I suggest the following pattern. Using it will allow auto-completion and type hinting to work properly.

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-----------------')

You cannot import classes A & B using this syntax

from playgroud.cyclic_import_a import A
from playground.cyclic_import_b import B

You cannot declare the type of parameter a in class B __ init __ method, but you can "cast" it this way:

def __init__(self, a):
    self.a: playground.cyclic_import_a.A = a
清晨说晚安 2024-08-14 17:44:52

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

Another solution is to use a proxy for the d_file.

For example, let's say that you want to share the blah class with the c_file. The d_file thus contains:

class blah:
    def __init__(self):
        print("blah")

Here is what you enter in 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()

And in a's 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() 
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文