如何处理Python中不同模块的相关对象之间的循环引用?

How to handle circular references between related objects from different modules in Python?

为了提高我的(初学者)Python 技能,我开始了一个宠物项目,现在我遇到了循环导入问题。

宠物项目是一款口袋妖怪式的小游戏,其中包括一群佩戴武器的动物。 关系链:团队->动物->武器(一个团队由几只动物组成,每只动物都持有一件武器)。 为了避免过大 类 我决定将非常不同的 类 动物和武器分布在两个文件中,并且我使用导入来相互访问。 来自 Java 我喜欢强类型变量、参数和参数。

所以精简了一点,我的 类 weapons.py 和 animals.py 看起来像这样:

import weapons
class Animal():
  def __init__(self, name: str, level: int):
    self.name: str = name
    self.level: int = int
    self.weapon: Weapon or None = None
  def equip(self, weapon: Weapon) -> None:
    self.weapon = weapon
import animals
from abc import ABC
class Weapon(ABC):
  def __init__(self, type: str, power_level: float):
    self.type: str = type
    self.power_level: float = power_level
    self.wielder: Animal or None = None
  def set_wielder(wielder: Animal) -> None:
    self.wielder = wielder

所以当我实例化动物时,我不希望它们立即使用武器,也不希望武器立即拥有所有者。但是虽然动物 -> 武器的关系在游戏中相当直接,但我也希望有一种方法可以从武器指向拥有它的动物。

以上代码导致循环导入问题。当面对一个不同但相关的问题时,我发现了有趣的 __future__ 模块。添加“from __future__ import annotations”解决了我的问题。

虽然我对我的工作代码很满意,但我想知道我是否可以以更优雅的方式解决这个问题。这是否是臭代码。是否有不同的解决方案仍然允许我使用打字。我很高兴收到任何可以改进我的 Python 编码风格(以及我对循环导入的理解)的建议

要了解如何构建代码,您可以从组合、聚合、关联的角度考虑。

What is the difference between association, aggregation and composition?

https://www.visual-paradigm.com/guide/uml-unified-modeling-language/uml-aggregation-vs-composition/

仍然有几种可能性,你需要决定哪一种是最重要的(owner HAS A weapon,weapon HAS A owner)。

假设每件武器一次只有一个所有者,您希望如何访问该武器?

owner.weapon -> 那你就认识主人了

或者您可以保留对所有者的引用作为武器的属性:

weapon.owned_by -> 也许在这里使用 id 而不是对实际 class 的引用,这就是你当前的问题,对吗?

没有主人的武器存在吗?然后看Composition:

Composition 意味着 child 不能独立于 parent 而存在的关系。

构图示例: 房子 (parent) 和房间 (child)。没有房子就没有房间。

非合成示例: 汽车和轮胎。没有汽车就有轮胎。

关于为什么要更好地避免循环引用的一般主题:https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references

你也可以尝试考虑依赖倒置(注入原理) (参见 herehere)。 我认为您已经在第一种方法中尝试过(将 Weapon 实例传递给 Animal)。这个想法很好,但也许你需要在中间加一层。

另一件事,来自 Java 你习惯了 getter 和 setter。那是 在 Python 中不太流行(但你可以做到)。

你的方法:

class Weapon(ABC):

  def set_wielder(wielder: Animal) -> None:
    self.wielder = wielder

更多Pythonic,使用属性("descriptors"):

class Weapon(ABC):

    def __init__(self):

        # notice the underscore, it indicates "treat as non-public"
        # but in Python there is no such thing
        self._wielder = None

    @property #this makes it work like a getter
    def wielder(self) -> Animal: # not sure about the annotation syntax
        return self._wielder

    @wielder.setter 
    def wielder(wielder: Animal) -> None:
        self._wielder = wielder   

您可以阅读描述符 herehere 以及更多的理论 here.