Python 中多重继承的优雅方式?
为了更好地使用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 个回答
从概念上讲,果汁(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)
我觉得这也回答了你第三个问题。
我们可以用三个东西来理解这个问题:Fruit
(水果)、Juice
(果汁)和Juicer
(榨汁机)。Juicer
的工作是把某种Fruit
变成对应类型的Juice
,比如说Orange => OrangeJuice
(橙子变成橙汁)。这样的话,你可以有两个类的层次结构,Orange
(橙子)是Fruit
(水果)的一个子类,而OrangeJuice
(橙汁)是Juice
(果汁)的一个子类,Juicer
就像是它们之间的桥梁。
这样做可以避免很多你原本提议的解决方案可能带来的麻烦。例如,如果我有一个叫Slicer
(切水果机)的类,它需要一些Fruit
来制作FruitSalad
(水果沙拉),也就是Fruit[] => FruitSalad
。在你提议的方案中,我可以给Slicer
一个OrangeJuice
的实例(你认为这也是一种Fruit
),然后期待它能做出美味的FruitSalad
,但结果却只得到一个湿漉漉的台面。
我觉得你第一个选项最接近一个好主意,不过这里有个改进的建议:
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
然后你可以扩展这个想法,考虑以下内容:
- 每种
Fruit
在Juice
中占多少; Juice
的体积是多少(可以是从成分计算得出的另一个@property
?);- 有哪些添加剂(比如水、糖等)。
使用一致的比例单位会比较合理,比如用每克Fruit
的糖分(即百分比)来表示sugar_content
。