Python在两个类之间创建一个manytone关系和一个相互引用的方法

2024-09-28 23:24:41 发布

您现在位置:Python中文网/ 问答频道 /正文

我有一个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类的实例。 每个车队有两名车手,车手可以交换车队。 我可以访问属于某个团队的驱动程序,但如何访问某个驱动程序所属的团队,而不必在Driver类中手动添加team参数


Tags: nameselfnonenumberinitdefdriver团队
2条回答

有很多方法可以解决这样的问题。许多代表着简单性(也许还有效率)和方便性(或“魔力”)之间的折衷。有时候,直截了当、直截了当确实是最好的,即使它看起来很笨拙

也就是说。。。实现这一点的一种方法是给Team一个class属性,它是一个字典,列出所有team实例及其关联的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()不是附加到特定实例,而是用于Team类的常规簿记的方法,因此它是class method

    @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

我们可以使用property decorator来轻松查看团队的驱动因素:

    @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的总体目标是成为一个web框架,但即使您没有做任何与web相关的事情,它的对象关系系统也实现了各种关系(一对一、一对多、多对多),具有许多方便的选项,如双向性和唯一性的实施。如果您想尝试一下,请参见此question。(我相信SQLAlchemy实现了类似的功能,但我自己还没有尝试过。)

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添加到驱动程序类中

编辑

正如@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类也将处理团队属性的设置

相关问题 更多 >