Python 中的循环导入依赖[重复]

2025-01-10 08:46:00
admin
原创
176
摘要:问题描述:假设我有以下目录结构:a\n __init__.py b\n __init__.py c\n __init__.py c_file.py d\n __init__.py ...

问题描述:

假设我有以下目录结构:

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

a包的中__init__.pyc包被导入。但是c_file.py导入了a.b.d

程序失败,在尝试导入b时说不存在。(它实际上不存在,因为我们正在导入它。)c_file.py`a.b.d`

如何解决此问题?


解决方案 1:

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

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

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

解决方案 2:

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

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

解决方案 3:

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

所以不要这样做

from models import Student

一是

from models import Classroom

另一种方式是

import models

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

解决方案 4:

由于类型提示导致的循环依赖

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

请注意,类型提示周围没有引号。由于Python 3.10 中注释的评估被推迟,这些类型提示被视为在引号中。

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

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

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

from __future__ import annotations

解决方案 5:

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

如果你确实想导入 abd,c/__init__.py可以通过将系统路径更改为 a 上方的一个目录并将导入更改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 不存在。您可以破解__int__.pyc 目录中的 sys.path 以指向顶级目录,然后导入__init__c 中的任何模块,以便能够使用完整路径导入 abd 我怀疑导入是否是好的做法,__init__.py但它对我的用例有效。

解决方案 6:

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

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

解决方案 7:

另一个解决方案是使用 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() 
相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3592  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2442  
  敏捷每日站会作为敏捷项目管理中的关键环节,对于提升产品生命周期管理(PLM)效率有着不可忽视的作用。PLM涵盖了产品从概念产生到最终报废的全过程管理,涉及众多环节与人员,而每日站会能够通过优化沟通机制,让信息在团队中快速、准确地流动,从而推动整个PLM流程更加顺畅、高效。接下来,我们将深入探讨如何通过四步优化沟通机制,...
plm系统   17  
  在企业的发展进程中,产品生命周期管理(PLM)项目管理至关重要,而数据驱动决策则是提升PLM项目管理效能的关键手段。通过运用合适的分析模型,企业能够从海量数据中挖掘有价值的信息,为决策提供有力支撑,进而优化产品全生命周期的各个环节。以下将详细介绍助力PLM项目管理实现数据驱动决策的5大分析模型。需求分析模型需求分析是P...
plm系统功能介绍   19  
  PLM(产品生命周期管理)系统在企业的产品研发、生产与运营中扮演着至关重要的角色。它涵盖了从产品概念设计到退役的全流程管理,确保产品数据的有效整合与协同。然而,在复杂多变的商业环境中,黑天鹅事件随时可能降临,给企业带来难以预估的冲击。这些意外事件具有不可预测性、极大的影响力和事后的可解释性等特点,会对PLM系统的正常运...
plm系统的主要功能模块   16  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用