Python 中多重继承的优雅方式?

4 投票
3 回答
587 浏览
提问于 2025-04-18 02:58

为了更好地使用Python和面向对象编程的风格,我想请教一下大家如何实现这个概念。

假设我有一个水果的基础类,比如苹果和香蕉,这个类包含一些基本属性,比如颜色。然后我想创建一个从水果类继承的果汁类,这个类会增加一些方法和属性,比如容量和糖分。

一个大致的结构可能是:

class Fruit(object):
def __init__(self, fruit_name):
    if fruit_name == 'apple': 
        self.color = 'green'
        self.sugar_content = 2
    elif fruit_name == 'banana':
        self.color = 'yellow'
        self.sugar_content = 3

然后我可以继承水果类的方法:

class Juice(Fruit):
    def __init___(self, fruit_name, volume, additives):
        PERHAPS I NEED TO USE A SUPER STATEMENT HERE TO PASS THE fruit_name parameter (which is 'apple' or 'banana' BACK TO THE FRUIT CLASS? I want this class to be able to access sugar_content and color of the the fruit class.
        self.volume = volume
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.sugar_content* self.volume # i.e. some arbitrary function using the class parameters of both this juice class and the original fruit class

最终,我可以创建一个对象,比如:

my_juice = Juice(fruit_name='apple', volume=200, additives='sugar,salt')
print 'My juice contains %f calories' % self.calories(sugar_added=5)
print 'The color of my juice, based on the fruit color is %s' % self.color

另外,我在想,是否更好不去继承,而是直接从果汁类调用水果类。例如:

class Juice(object):
    def __init__(self, fruit_name, volume, additives):
        self.fruit = Fruit(fruit_name=fruit_name)
        self.volume = volume # ASIDE: is there an easier way to inherit all parameters from a init call and make them into class variables of the same name and accessible by self.variable_name calls?
        self.additives = additives

    def calories(self, sugar_added):
        return sugar_added + 2*self.fruit.sugar_content* self.volume

从某种程度上来说,上面的方式感觉更自然,因为self.Fruit.sugar_content直接表明糖分是水果的一个属性。而如果我继承了水果类,我就会用self.sugar_content,这个属性虽然是水果的,但可能会和果汁类的糖分混淆,因为果汁的糖分还受其他因素的影响。

或者,是否更好为每种水果单独创建一个类,并把评估传给果汁类的fruit_name字符串的逻辑放在果汁类的init方法中,然后使用例如:

class Apple(object):
   self.color = 'green'

class Banana(object):
    self.color = 'yellow'

class Juice(object):
    def __init__(self, fruit_name, other params):
         if fruit_name == 'apple':
             self.Fruit = Apple
             self.sugar_content=2
         elif fruit_name == 'banana':
             self.Fruit = Banana
             self.sugar_content=3
         # Or some easier way to simply say
             self.Fruit = the class which has the same name as the string parameter fruit_name

我明白以上所有方法在理论上都可行,但我希望能得到一些建议,以便发展出一种高效的编码风格。在实际应用中,我想把这个应用到一个更复杂的项目中,虽然这个例子涵盖了我面临的许多问题。

欢迎所有建议、提示或推荐的阅读链接。谢谢。

3 个回答

2

从概念上讲,果汁(Juice)不应该继承自水果(Fruit),但为了你的问题,我们假设这样做是有道理的。在你第一个代码示例中,你必须调用

super(Juice,self).__init__(fruit,name)

在Python 3中,这样写就是

super().__init__(fruit,name)

现在来说说你的第二个问题。组合(Composition)才是正确的做法。果汁是由水果组成的,但它不应该负责去创建水果

class Juice(object):
    def __init__(self, fruit, volume, additives):
        self.fruit = fruit
        self.volume = volume
        self.additives = additives

这样你就可以:

apple = Apple(...)
apple_juice= Juice(apple,2,None)

我觉得这也回答了你第三个问题。

3

我们可以用三个东西来理解这个问题:Fruit(水果)、Juice(果汁)和Juicer(榨汁机)。Juicer的工作是把某种Fruit变成对应类型的Juice,比如说Orange => OrangeJuice(橙子变成橙汁)。这样的话,你可以有两个类的层次结构,Orange(橙子)是Fruit(水果)的一个子类,而OrangeJuice(橙汁)是Juice(果汁)的一个子类,Juicer就像是它们之间的桥梁。

这样做可以避免很多你原本提议的解决方案可能带来的麻烦。例如,如果我有一个叫Slicer(切水果机)的类,它需要一些Fruit来制作FruitSalad(水果沙拉),也就是Fruit[] => FruitSalad。在你提议的方案中,我可以给Slicer一个OrangeJuice的实例(你认为这也是一种Fruit),然后期待它能做出美味的FruitSalad,但结果却只得到一个湿漉漉的台面。

4

我觉得你第一个选项最接近一个好主意,不过这里有个改进的建议:

class Fruit:

    def __init__(self, name, sugar_content):
        self.name = name
        self.sugar_content = sugar_content

现在,判断每个Fruit含有多少糖的逻辑完全移到了类定义之外。那它可以放在哪里呢?一个选择是创建一个子类:

class Apple(Fruit):

    def __init__(self):
        super().__init__("apple", 2)

另外,如果Fruit之间唯一的区别是名字和糖分,那就直接创建实例并输入数据(比如从文件中读取):

apple = Fruit("apple", 2)

接下来谈谈Juice。它是由Fruit组成的,但实际上并不是Fruit,所以继承不太合适。可以尝试:

class Juice:

    def __init__(self, fruits):
        self.fruits = fruits

    @property
    def sugar_content(self):
        return sum(fruit.sugar_content for fruit in self.fruits)

你可以传入一个Fruit实例的可迭代对象来制作Juice

>>> mixed_fruit = Juice([Fruit("apple", 2), Fruit("banana", 3)])
>>> mixed_fruit.sugar_content
5

然后你可以扩展这个想法,考虑以下内容:

  • 每种FruitJuice中占多少;
  • Juice的体积是多少(可以是从成分计算得出的另一个@property?);
  • 有哪些添加剂(比如水、糖等)。

使用一致的比例单位会比较合理,比如用每克Fruit的糖分(即百分比)来表示sugar_content

撰写回答