Python - Создание отношения «многие к одному» между двумя классами и методом ссылки друг на друга. - PullRequest
1 голос
/ 14 июля 2020

У меня есть класс Team и класс Driver.

class Team:
  def __init__(self, teamName, drivers=None):
      self.teamName = teamName
      if drivers is not None:
        self.drivers = list(drivers)
      else:
        self.drivers = []
class Driver:
    def __init__(self, name, country, number: str, podiums: int):
        self.name = name
        self.country = country
        self.number = number
        self.podiums = podiums

Класс Team также имеет функцию для добавления экземпляра класса Driver в конкретная команда. В каждой команде по два гонщика, и гонщики могут менять команды. Я могу получить доступ к драйверам, принадлежащим определенной команде, но как мне получить доступ к команде, к которой принадлежит определенный драйвер, без необходимости вручную добавлять параметр team в класс Driver?

Ответы [ 2 ]

1 голос
/ 14 июля 2020

Есть много способов решить такую ​​проблему. Многие представляют собой компромисс между простотой (и, возможно, эффективностью), с одной стороны, и удобством (или "magi c"), с другой. Иногда лучше всего быть прямым и явным, даже если это кажется неуклюжим. и связанные с ними driver экземпляры. На основании вашего вопроса я делаю вывод, что водитель может одновременно находиться только в одной команде, и, если возможно, вы хотите, чтобы это выполнялось как можно «автоматически». Я не уверен, хотите ли вы, чтобы код обеспечивал соблюдение «не более двух гонщиков на команду», что может приводить к ошибке, если вы попытаетесь добавить третьего, поэтому я пока оставлю этот аспект.

class Team:
    _membership = {} # Stores all teams and their members at the class level.
                     # The underscore indicates it's only intended to be used 
                     # internally by the Team class.
    
    def __init__(self, name, drivers=None):
        self.name = name
        Team._membership[self] = [] # Create an entry in the dictionary, with no drivers
        if drivers is None:
            drivers = []
        for driver in drivers:
            self.add_driver(driver) # Defined below
    
    def __repr__(self):
        """Define how to represent this object in output."""
        return f"<Team '{self.name}', drivers: {self.drivers}>"

Нам нужно реализовать этот add_driver() метод. Он проверит, находится ли драйвер в настоящее время в другой команде, и если да, удалит его там:

    def add_driver(self, driver):
        old_team = Team.find_team_for_driver(driver) # Defined below
        if old_team == self:
            # If the driver is already on this team, we're done.
            return
        if old_team is not None:
            # If it was on a different team, remove it from that team.
            old_team.drivers.remove(driver)
        # Lastly, add it to this team.
        Team._membership[self].append(driver)

find_team_for_driver() не привязан к конкретному экземпляру c, а является методом для общего учета класса Team, так что это метод класса :

    @classmethod
    def find_team_for_driver(cls, driver):
        for team, drivers in cls._membership.items():
            if driver in drivers:
                return team
        # Return None if the driver was not found in a team.
        return None

Мы можем использовать декоратор свойств , чтобы упростить просмотр, какие драйверы команда has:

    @property
    def drivers(self):
        return Team._membership[self]
    
    @drivers.setter
    def drivers(self, drivers):
        Team._membership[self] = []
        for driver in drivers:
            Team.update_membership(self, driver)

Это означает, что мы все еще можем использовать простой синтаксис team.drivers, а внутренние компоненты класса Team будут обрабатывать его выборку из словаря _membership. Обратите внимание, что, хотя заманчиво думать, что вы могли бы добавить драйверов в команду с team.drivers.append(new_driver), они не будут знать, что нужно удалить драйвер из своей предыдущей команды, если таковой имеется. Обязательно используйте вместо него team.add_driver(new_driver).

(обратите внимание, что team.drivers.remove(driver_to_remove) работает , но мы могли бы определить метод remove_driver(), если хотите, просто для согласованности.)

Хорошо, мы, наконец, добрались до класса Driver, который мало что изменился. (Я удалил все атрибуты, кроме name, просто чтобы сделать этот пример кода немного проще.) Мы можем использовать свойство, чтобы узнать, в какой команде находится драйвер, а также переназначить его новой команде:

class Driver:
    def __init__(self, name):
        self.name = name
    
    def __repr__(self):
        return f"<Driver '{self.name}'>"
    
    @property
    def team(self):
        return Team.find_team_for_driver(self)
    
    @team.setter
    def team(self, team):
        team.add_driver(self)

Тестирование:

bob = Driver('Bob')
lisa = Driver('Lisa')
kaya = Driver('Kaya')

red = Team('Red Team', [bob, lisa])
print(red)
=> <Team 'Red Team', drivers: [<Driver 'Bob'>, <Driver 'Lisa'>]>

blue = Team('Blue Team')
blue.add_driver(kaya)
print(blue)
=> <Team 'Blue Team', drivers: [<Driver 'Kaya'>]>

Переназначение драйверов:

lisa.team = blue
red.add_driver(kaya)

print(red)
=> <Team 'Red Team', drivers: [<Driver 'Bob'>, <Driver 'Kaya'>]>
print(blue)
=> <Team 'Blue Team', drivers: [<Driver 'Lisa'>]>

Такая инфраструктура может быть полезна для проекта определенного размера и сложности. Для более мелких и простых вещей вам, вероятно, лучше явно определять и устанавливать связи между объектами. (Обратите внимание, что все итерации, хотя Team._membership медленнее, чем ссылка на явные атрибуты объекта. Во многих случаях этого может быть недостаточно, чтобы иметь значение.)

На другом конце спектра, как Проект становится более сложным, вместо того, чтобы реализовывать все больше и больше этого материала, существующая система, такая как Django, начинает иметь больше смысла. Общая цель Django - быть веб-фреймворком, но даже если вы не делаете ничего связанного с сетью, его объектно-реляционная система реализует всевозможные отношения (один-к-одному, один-ко-многим, многие-ко-многим), с множеством удобных опций, таких как двунаправленность и обеспечение уникальности. См. Этот вопрос , если вы дойдете до точки, где захотите попробовать это. (Я считаю, что SQLAlchemy реализует нечто подобное, но я сам не пробовал.)

0 голосов
/ 14 июля 2020
class Team:
  def __init__(self, teamName, drivers=None):
      self.teamName = teamName
      self.drivers = list(drivers or [])
      for driver in self.drivers:
          setattr(driver, "team", self)

Установите атрибут команды для каждого водителя как соответствующий экземпляр «команды» всякий раз, когда создается экземпляр группы. Кроме того, добавьте team = None в Driver class

Edit

Как указал @crazychunk, объекты драйвера могут быть добавлены в список драйверов отдельно как хорошо, и с ним нужно обращаться. Итак, я предлагаю другое решение.

class Drivers(list):

    def __init__(self, *args, team, **kwargs):
         super(Drivers, self).__init__(*args, *kwargs)
         self.team = team
         for driver in self:
             driver.team = team

    def append(self, p_object):
         super(Drivers, self).append(p_object)
         p_object.team = self.team
       
    def extend(self, iterable):
         super(Drivers, self).extend(iterable)
         for x in iterable:
             x.team = self.team

class Team:
    def __init__(self, teamName, drivers=None):
        self.teamName = teamName
        self.drivers = Drivers(drivers or [], team=self)

class Driver:
    def __init__(self, name, country, number: str, podiums: int):
        self.name = name
        self.country = country
        self.number = number
        self.podiums = podiums

Таким образом, даже если в команду добавляются новые драйверы, класс Drivers будет обрабатывать установку атрибута команды.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...