简体   繁体   English

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

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

In an effort to improve my (beginner) Python skills I started a pet project and now I am having trouble with circular import issues.为了提高我的(初学者)Python 技能,我开始了一个宠物项目,现在我遇到了循环导入问题。

The pet project is a little pokemon-esque game that features among other things teams of animals wearing weapons.宠物项目是一款口袋妖怪式的小游戏,其中包括穿着武器的动物团队。 The relationship chain: team -> animal -> weapon (a team consists of a few animals, each animal wields a weapon).关系链:团队 -> 动物 -> 武器(一个团队由几只动物组成,每只动物持有一把武器)。 To avoid overly huge classes I decided to spread the very different classes of animal and weapon over two files and I use import to access each other.为了避免过大的类,我决定将非常不同的动物和武器类分布在两个文件中,并使用导入来相互访问。 Coming from Java I like to strong-type variables, arguments, and parameters.来自 Java 我喜欢强类型变量、arguments 和参数。

So stripped down a bit, my classes weapons.py and animals.py look like this:所以剥离了一点,我的类武器.py 和动物.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

So when I instantiate animals, I don't want them to wield weapons right away nor do I want weapons to have owners right away.因此,当我实例化动物时,我不希望它们立即使用武器,也不希望武器立即拥有所有者。 But while the relationship animal -> weapon is rather straight forward in the game, I also want to have a way to point from a weapon to the animal that owns it.但是,虽然动物 -> 武器的关系在游戏中相当直接,但我也希望有一种方法可以从武器指向拥有它的动物。

The code above causes circular import issues.上面的代码会导致循环导入问题。 When facing a different yet related issue I found the interesting __future__ module.当面临一个不同但相关的问题时,我发现了有趣的__future__模块。 Adding " from __future__ import annotations " resolved my problem.添加“ from __future__ import annotations ”解决了我的问题。

But while I am happy about my working code, I wonder whether I could have solved this issue in a more elegant manner.但是,虽然我对我的工作代码感到高兴,但我想知道我是否可以以更优雅的方式解决这个问题。 Whether this is smelly code.这是否是臭代码。 Whether there is a different solution to this all that still allows me to use typing.是否有不同的解决方案仍然允许我使用打字。 I am happy about any advice that improves my Python coding style (and my understanding of circular imports)我很高兴有任何建议可以改进我的 Python 编码风格(以及我对循环导入的理解)

To get an idea of how to structure your code you could think in terms of composition, aggregation, association.要了解如何构建代码,您可以从组合、聚合、关联方面进行思考。

What is the difference between association, aggregation and composition? 关联,聚合和组合之间有什么区别?

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

Still there are several possibilities, you need to decide which is the most important one (owner HAS A weapon, weapon HAS A owner).仍然有几种可能性,您需要确定哪个是最重要的(拥有者有武器,武器有拥有者)。

Say every weapon only has one owner at a time, how to you want to access the weapon?假设每个武器一次只有一个拥有者,你想如何访问武器?

owner.weapon -> then you know the owner owner.weapon -> 那么你就知道所有者了

Or you could keep a reference to the owner as attribute of the weapon:或者您可以保留对所有者的引用作为武器的属性:

weapon.owned_by -> maybe use an id here not a reference to the actual class, that's what your current problem is, right? weapon.owned_by -> 可能在这里使用id而不是对实际 class 的引用,这就是您当前的问题,对吧?

Does a weapon exist without an owner?没有所有者的武器是否存在? Then look at Composition :然后看组成

Composition implies a relationship where the child cannot exist independent of the parent.组合意味着孩子不能独立于父母而存在的关系。

Example for a composition: House (parent) and Room (child).组合示例:House(父)和 Room(子)。 Rooms don't exist without a house.没有房子就没有房间。

Example for not a composition: Car and Tire.非组合示例:汽车和轮胎。 Tires exist without cars.轮胎没有汽车。

A general thread on why to better avoid circular references: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references关于为什么更好地避免循环引用的一般线程: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references

You can also try to consider the Dependency Inversion (Injection Principle) (see here or here ).您还可以尝试考虑依赖倒置(注入原则) (参见此处此处)。 I think you already tried that in your first approach (passing a Weapon instance into Animal).我认为您已经在第一种方法中尝试过(将 Weapon 实例传递给 Animal)。 The idea was fine, but maybe you need another layer inbetween.这个想法很好,但也许你需要在两者之间再增加一层。

Another thing, coming from Java you are used to getters and setters.另一件事,来自 Java,你习惯于 getter 和 setter。 That is not that popular in Python, (but you could do it).这在 Python 中并不流行(但你可以做到)。

Your approach:你的方法:

class Weapon(ABC):

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

More Pythonic, use Properties ("descriptors"):更多 Pythonic,使用属性(“描述符”):

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   

You can read about descriptors here , here and with a bit more theory here .您可以在此处此处阅读有关描述符的信息,并在此处了解更多理论。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM