Python中的循环依赖

Posted

技术标签:

【中文标题】Python中的循环依赖【英文标题】:Circular dependency in Python 【发布时间】:2010-10-28 01:10:14 【问题描述】:

我有两个文件,node.pypath.py,它们分别定义了两个类,NodePath

到今天为止,Path 的定义引用了Node 对象,因此我已经完成了

from node.py import *

path.py 文件中。

但是,从今天开始,我为 Node 创建了一个新方法,该方法引用了 Path 对象。

我在尝试导入path.py 时遇到问题:我试过了,当程序运行并调用使用NodePath 方法时,出现了一个关于Node 未定义的异常。

我该怎么办?

【问题讨论】:

重复? ***.com/questions/744373/python-cyclic-imports 您是否要为每个文件创建一个类?这就是为什么这很少能奏效的原因。 同意 S.Lott。 Python 不是 Java。每个文件不需要一个类。 有几个人说过“每个文件不需要一个类”,大意是“不要尝试成为 Java”。好的-但这不是重点。类定义可能会变得非常大,并且将它们捆绑到同一个文件中会产生非常大的、不可读的文件。在我正在处理的一个程序中,有 8 个相互依赖的类,每个类都有几百行长,我认为将它们保存在同一个文件中没有任何好处,而将它们分开则有相当大的好处。 Could not upvote @sfkleach enough.. 好像多类文件中的 1,000+ LOC 不足以阅读或维护,测试文件(人们 为 Python 类编写测试,对吗?)将会更多更长,甚至更多是维护的噩梦。仅仅因为 Python 有时会使良好的组织变得困难并不意味着我们应该放弃可维护的代码。 【参考方案1】:

Importing Python Modules 是一篇很棒的文章,它解释了 Python 中的循环导入。

解决此问题的最简单方法是将路径导入移动到节点模块的末尾。

【讨论】:

好的,但问题是,我在那个包中还有另外两个模块 tree.pyblock.py 需要 node.py 并且是 path.py 所需要的。那么我应该将它们全部放在一个文件中吗?我喜欢每个班级有一个模块。 你试过我的建议了吗?它可能会起作用。只需将导入移动到文件末尾即可。我建议您阅读这篇文章以了解发生这种情况的原因。 顺便说一句,除了你“喜欢”你想要每个模块一个类之外,还有其他原因吗?我见过这种偏好的几次,是因为它类似于 Java。 导入 Python 模块的链接已损坏。 这篇文章的最新存档是:web.archive.org/web/20200917011425/https://effbot.org/zone/…,之后网络就无法使用了。不幸的是,最新的更新日期仍然是 2001-02-02,这使得这篇文章非常陈旧。例如。不建议将__import__() 用于正常任务。见docs.python.org/3/library/functions.html#__import__【参考方案2】:

另一种方法是仅在函数中导入两个模块中的一个,而您在另一个模块中需要它。当然,如果您只在一个或少数几个函数中需要它,这种方法效果最好:

# in node.py 
from path import Path
class Node 
    ...

# in path.py
class Path
  def method_needs_node(): 
    from node import Node
    n = Node()
    ...

【讨论】:

它会工作,但我没有看到这个通过可读性代码审查【参考方案3】:

您可能不需要在node.py 中导入Path 以便PathNode 相互使用。

# in __init__.py  (The order of imports should not matter.)
from .node import Node
from .path import Path

# in path.py 
from . import Node
class Path
  ...

  def return_something_pathy(self): 
    ...

# in node.py
class Node
  def __init__(self, path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

要明确Node 正在使用Path,请添加类型提示。从 Python 3.7 开始有一个功能可以支持类型注释中的前向引用,如PEP 563 中所述。

# in node.py  (Now with type hinting.)
from __future__ import annotations

class Node
  def __init__(self, path: Path): 
    self.path = path
    ...

  def a_node_method():
    print(self.path.return_something_pathy())

I came across a Yet another solution to dig you out of a circular import hole in Python 是一篇很棒的博文,它教会了我这一点。

【讨论】:

【参考方案4】:

我更喜欢通过在另一个依赖类的构造函数中声明一个依赖项来打破循环依赖项。在我看来,这使代码更整洁,并且可以轻松访问所有需要依赖项的方法。

所以在我的例子中,我有一个相互依赖的 CustomerService 和一个 UserService。我打破循环依赖如下:

class UserService:

    def __init__(self):
        # Declared in constructor to avoid circular dependency
        from server.portal.services.admin.customer_service import CustomerService
        self.customer_service = CustomerService()

    def create_user(self, customer_id: int) -> User:
        # Now easy to access the dependency from any method
        customer = self.customer_service.get_by_id(customer_id)

【讨论】:

【参考方案5】:

另一种方法是在同一个模块中定义它们,并延迟定义类型。有点像这样:

class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    def method_needs_node(self):
       n = Node()

Node._path_type = Path

在这方面对称可能会更好:

class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...


class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = Node()

Node._path_type = Path
Path._node_type = Node

这也可以在多个模块中完成:

# in node.py
class Node:
   _path_type: type = None
   
   def method_needs_path(self):
       p = self._path_type()
       ...

# in path.py
from .node import Node

class Path:
    _node_type: type = None

    def method_needs_node(self):
       n = self._node_type()

Node._path_type = Path
Path._node_type = Node

# in __init__.py (note that order is important now)
from .node import Node
from .path import Path

【讨论】:

以上是关于Python中的循环依赖的主要内容,如果未能解决你的问题,请参考以下文章

Python相互依赖的类(循环依赖)

Python中的循环依赖

Python中的循环导入依赖

[Python] 迭代器是什么?你每天在用的for循环都依赖它!

在python logger中获取双重日志 - 代码具有循环依赖性

依赖于 Python 中前一个循环的任意数量的嵌套循环