Python中继承的意义是什么?
假设你遇到了以下情况
#include <iostream>
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
void speak() { std::cout << "woff!" <<std::endl; }
};
class Cat : public Animal {
void speak() { std::cout << "meow!" <<std::endl; }
};
void makeSpeak(Animal &a) {
a.speak();
}
int main() {
Dog d;
Cat c;
makeSpeak(d);
makeSpeak(c);
}
如你所见,makeSpeak是一个接受通用Animal对象的例程。在这个例子中,Animal有点像Java中的接口,因为它只包含一个纯虚方法。makeSpeak并不知道它接收到的Animal是什么类型的。它只是发送一个“说话”的信号,然后让后期绑定来决定调用哪个方法:要么是Cat::speak(),要么是Dog::speak()。这意味着,对于makeSpeak来说,实际传入的是哪个子类并不重要。
那么Python呢?我们来看一下在Python中相同情况的代码。请注意,我尽量让它与C++的例子保持一致:
class Animal(object):
def speak(self):
raise NotImplementedError()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
在这个例子中,你可以看到相同的策略。你使用继承来利用狗和猫都是动物这一层次概念。 但在Python中,不需要这种层次结构。这同样有效
class Dog:
def speak(self):
print "woff!"
class Cat:
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
在Python中,你可以对任何你想要的对象发送“说话”的信号。如果这个对象能够处理这个信号,它就会执行;否则就会抛出异常。假设你在两个代码中都添加了一个Airplane类,并将一个Airplane对象提交给makeSpeak。在C++的情况下,它不会编译,因为Airplane并不是Animal的派生类。而在Python的情况下,它会在运行时抛出异常,这甚至可能是预期的行为。
另一方面,假设你添加了一个MouthOfTruth类,并且它有一个speak()方法。在C++的情况下,你要么需要重构你的层次结构,要么需要定义一个不同的makeSpeak方法来接受MouthOfTruth对象,或者在Java中你可以提取行为到一个CanSpeakIface接口,并为每个实现这个接口。有很多解决方案……
我想指出的是,我还没有找到在Python中使用继承的单一理由(除了框架和异常树,但我想还有其他替代策略)。你不需要实现一个基类-派生类的层次结构来实现多态。如果你想使用继承来重用实现,你可以通过包含和委托来实现同样的效果,额外的好处是你可以在运行时更改它,并且你可以清楚地定义被包含对象的接口,而不必担心意外的副作用。
所以,最后,问题是:在Python中继承的意义何在?
编辑:感谢大家非常有趣的回答。确实,你可以用它来重用代码,但我在重用实现时总是很谨慎。一般来说,我倾向于做非常浅的继承树,或者根本不做树,如果某个功能是公共的,我会将其重构为一个公共模块例程,然后从每个对象中调用它。我确实看到有一个单一的变更点的好处(例如,不需要在Dog、Cat、Moose等中添加,而只需添加到Animal,这就是继承的基本优势),但你也可以通过委托链来实现同样的效果(例如,像JavaScript那样)。不过我并不是说这更好,只是另一种方式。
我还发现了一个类似的帖子。
11 个回答
在Python中,继承更多的是一种方便的功能,而不是必须的东西。我觉得它最好的用法是给一个类提供“默认行为”。
实际上,有很多Python开发者认为根本不应该使用继承。无论你怎么做,都不要过度使用。类的层级结构如果太复杂,就很容易被人称为“Java程序员”,这可不是你想要的。:-)
在Python中,继承就是为了重复利用代码。你可以把一些共同的功能放到一个基础类里,然后在派生类中实现不同的功能。
你提到的运行时鸭子类型被称为“重写”继承,但我认为继承作为一种设计和实现的方法,有它自己的优点,它是面向对象设计中不可或缺的一部分。在我看来,能否用其他方式实现某个功能并不是很重要,因为实际上你可以在Python中不使用类、函数等来编程,但问题在于你的代码设计得有多好,是否健壮,是否易读。
我可以举两个例子,说明在我看来继承是正确的做法,我相信还有更多的例子。
首先,如果你编程得当,你的makeSpeak函数可能想要验证它的输入确实是一个动物,而不仅仅是“它会说话”。在这种情况下,最优雅的方法就是使用继承。你当然可以用其他方式实现,但这就是面向对象设计和继承的美妙之处——你的代码会“真正”检查输入是否是“动物”。
第二个例子,更加简单明了,就是封装——这是面向对象设计的另一个重要部分。当父类有数据成员和/或非抽象方法时,这一点就变得重要了。举个简单的例子,假设父类有一个函数(speak_twice),它调用一个抽象函数:
class Animal(object):
def speak(self):
raise NotImplementedError()
def speak_twice(self):
self.speak()
self.speak()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
假设
如果父类有一个数据成员,比如
再说一次,我相信还有无数更多的例子,但基本上,每个基于扎实的面向对象设计的大型软件都需要继承。