Как обрабатывать циклические ссылки между связанными объектами из разных модулей в Python? - PullRequest
1 голос
/ 07 апреля 2020

Чтобы улучшить свои навыки (для начинающих) Python, я запустил проект для домашних животных, и теперь у меня проблемы с циклическим импортом.

Проект для домашних животных - это небольшая игра в стиле покемонов, в которой есть среди прочего команды животных носят оружие. Цепочка отношений: команда -> животное -> оружие (команда состоит из нескольких животных, каждое животное владеет оружием). Чтобы избежать чрезмерно больших классов, я решил распределить очень разные классы животных и оружия по двум файлам, и я использую импорт для доступа друг к другу. Исходя из Java Мне нравятся переменные, аргументы и параметры сильного типа.

Итак, я немного урезан, мои классы arms.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 (и мое понимание циклического импорта)

1 Ответ

0 голосов
/ 07 апреля 2020

Чтобы получить представление о том, как структурировать ваш код, вы могли бы подумать с точки зрения композиции, агрегации, ассоциации.

В чем разница между объединением, агрегацией и составом?

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

Тем не менее есть несколько возможностей, вам нужно решить, какой из них самый важный (у владельца есть оружие, у оружия есть владелец).

Скажем, у каждого оружия есть только один владелец за раз, как вы хотите получить доступ к оружию?

owner.weapon -> тогда вы знаете владельца

Или вы можете оставить ссылку на владельца в качестве атрибута оружия:

weapon.owned_by -> возможно использовать id здесь не ссылка на реальный класс, это ваша текущая проблема, верно?

Существует ли оружие без владельца? Затем посмотрите на Композиция :

Композиция подразумевает отношения, в которых ребенок не может существовать независимо от родителя.

Пример композиции: Дом (родитель) и Комната (ребенок). Комнаты не существуют без дома.

Пример не композиции: Автомобиль и Шина. Шины существуют без автомобилей.

Общая тема о том, почему лучше избегать циклических ссылок: https://softwareengineering.stackexchange.com/questions/11856/whats-wrong-with-circular-references

Вы также можете попробовать рассмотреть инверсию зависимости ( Принцип впрыска) (см. здесь или здесь ). Я думаю, что вы уже попробовали это при первом подходе (передача экземпляра оружия в Animal). Идея была прекрасной, но, возможно, вам нужен еще один промежуточный слой.

Еще одна вещь, исходя из Java, вы привыкли к геттерам и сеттерам. Это не так популярно в Python, (но вы могли бы это сделать).

Ваш подход:

class Weapon(ABC):

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

Больше Pythoni c, используйте Свойства ("дескрипторы"):

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   

Вы можете прочитать о дескрипторах здесь , здесь и с немного большей теорией здесь .

...