Python中的inheritance点是什么?

假设你有以下情况

#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不知道它通过的动物的性质。 它只是发送信号“发言”,并留下后期绑定,以照顾到哪种方法调用:要么Cat :: speak()要么Dog :: speak()。 这就意味着,就makepepe而言,关于哪个子类实际通过的知识是无关紧要的。

但是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) 

现在,在这个例子中,你看到了相同的策略。 你使用inheritance来利用狗和猫的动物的层次概念。 但在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中,您可以将信号“发言”发送给您想要的任何对象。 如果对象能够处理它,它将被执行,否则会引发exception。 假设你为这两个代码添加一个类飞机,并提交一个飞机对象makeSpeak。 在C ++的情况下,它不会编译,因为飞机不是Animal的派生类。 在Python的情况下,它会在运行时引发一个exception,甚至可能是一个预期的行为。

另一方面,假设你用方法speak()添加一个MouthOfTruth类。 在C ++的情况下,你将不得不重构你的层次结构,或者你将不得不定义一个不同的makeSpeak方法来接受MouthOfTruth对象,或者你可以在java中将行为提取到CanSpeakIface中并为每个对象实现接口。 有很多解决scheme…

我想指出的是,我还没有find一个单一的理由来使用Python的inheritance(除了框架和树的exception,但我猜是存在替代策略)。 您不需要实现基于派生的层次结构来执行多态。 如果要使用inheritance来重新使用实现,则可以通过包含和委派完成相同的操作,还可以在运行时对其进行更改,并明确定义包含的接口,而不会冒非预期的副作用。

所以,最后,问题是:Python中的inheritance点是什么?

编辑 :感谢非常有趣的答案。 事实上,你可以使用它来重用代码,但是在重用实现时我总是小心谨慎。 一般来说,我倾向于做非常浅的inheritance树或根本没有树,如果一个常见的function,我重构它作为一个普通的模块例程,然后从每个对象调用它。 我确实看到了有一点改变的好处(比如,join了Dog,Cat,Moose等等,我只是join了Animal,这是inheritance的基本优势),但是你也可以实现一个代表链(例如JavaScript)。 我不是说它好一些,只是另一种方式。

我也在这方面find了一个类似的post 。

你所说的鸭式运行是“重载”inheritance,但是我认为inheritance作为一种devise和实现方法有其自身的优点,是面向对象devise的一个组成部分。 在我看来,你是否可以实现其他的东西并不是非常相关,因为实际上你可以在不使用类,函数等的情况下编写Python代码,但是问题在于你的代码是如何精心devise,健壮和可读的。

我可以举两个例子,我认为inheritance是正确的方法,我相信还有更多。

首先,如果你明智地编写代码,你的makeSpeak函数可能要validation它的input确实是一个Animal,而不仅仅是“它可以说”,在这种情况下,最优雅的方法是使用inheritance。 再次,你可以用其他方式来完成,但这就是带inheritance的面向对象devise的美妙之处 – 你的代码将“真正”检查input是否是“动物”。

第二,显然更直接的是封装 – 面向对象devise的另一个组成部分。 当祖先具有数据成员和/或非抽象方法时,这变得相关。 拿下面这个愚蠢的例子来说,祖先有一个调用抽象函数的函数(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" 

假设"speak_twice"是一个重要的function,你不想在狗和猫都​​编码,我相信你可以推断这个例子。 当然,你可以实现一个Python独立函数,它将接受一些duck-typed对象,检查它是否有一个speak函数并且调用它两次,但是这不是优雅的,并且错过了第一个点(validation它是一个Animal)。 更糟的是,为了加强封装的例子,如果后代类的成员函数想用"speak_twice"呢?

如果祖先类有一个数据成员,比如"print_number_of_legs"中的非抽象方法所使用的"number_of_legs" "print_number_of_legs" ,但是在后代类的构造函数中启动(例如,Dog将初始化它4而Snake会用0初始化它)。

我再次肯定有更多的例子,但基本上每个(足够大的)基于固体面向对象devise的软件都需要inheritance。

Python中的inheritance关系到代码的重用。 将通用function分解为基类,并在派生类中实现不同的function。

Python中的inheritance比其他任何方面都更为方便。 我发现最好是用“默认行为”来提供一个类。

的确,Python开发者有一个重要的社区,他们反对使用inheritance。 无论你做什么,不要只是不要过分。 有一个过于复杂的类层次结构是一个肯定的方式来标记为“Java程序员”,你不能拥有。 🙂

我认为在Python中的inheritance点不是让代码编译,这是inheritance的真正原因是将类扩展到另一个子类,并重写基类中的逻辑。 然而,在Python中打鸭子使得“接口”概念没有用处,因为你可以在调用之前检查方法是否存在,而不需要使用接口来限制类结构。

我认为用这样的抽象例子来给出一个有意义的,具体的答案是非常困难的。

为了简化,有两种types的inheritance:接口和实现。 如果你需要inheritance这个实现,那么python和静态types的OO语言(如C ++)并没有太大的区别。

界面的inheritance是一个很大的差异,根据我的经验,软件的devise会产生根本性的后果。 像Python这样的语言在这种情况下并不强迫你使用inheritance,在大多数情况下避免inheritance是一个好的方面,因为在后面修改错误的devise是非常困难的。 这是一个众所周知的在任何好的OOP书中提出的观点。

有些情况下,在Python中build议使用inheritance接口,例如插件等。对于这些情况,Python 2.5及以下版本缺乏“内置”的优雅方法,几个大框架devise了自己的解决scheme(zope,trac,twister)。 Python 2.6及以上版本有ABC类来解决这个问题 。

在C ++ / Java /等中,多态是由inheritance引起的。 放弃那种不正确的信念,dynamic的语言向你敞开。

实质上,在Python中,没有任何接口像“理解某些方法是可调用的”。 漂亮的手工波浪和学术探测,不是吗? 这意味着,因为你称之为“说话”,你显然希望对象应该有一个“说话”的方法。 简单,嗯? 这就是Liskov-ian,因为一个类的用户定义了它的接口,这个良好的devise概念可以引导你进入更健康的TDD。

所以剩下的就是另外一个海报礼貌地设法避免说一个代码共享的伎俩。 你可以把相同的行为写入每个“孩子”class,但这是多余的。 更容易inheritance或混合在inheritance层次结构中不变的function。 较小的DRY-er码一般来说是较好的。

鸭子打字没有意义,它是接口 – 就像你在创build一个全抽象的动物课堂时select的那样。

如果你使用了一个动物课,为其后代引入了一些真实的行为,那么引入了一些额外行为的狗和猫class将是这两个class的理由。 只有在祖先类没有提供后代类的实际代码的情况下,你的论点是正确的。

因为Python可以直接知道任何对象的能力,而且由于这些能力是可以超越类定义的,所以使用纯粹的抽象接口来“告诉”程序可以调用什么方法的想法是毫无意义的。 但这不是唯一的,甚至是主要的inheritance点。

你可以绕过Python的inheritance和几乎任何其他语言。 这是关于代码重用和代码简化的。

只是一个语义技巧,但是在构build好你的类和基类之后,你甚至不必知道你的对象有什么可能,看看你能不能做到。

说你有一个狗,这是一个分类动物的狗。

 command = raw_input("What do you want the dog to do?") if command in dir(d): getattr(d,command)() 

如果input的用户可用,代码将运行正确的方法。

使用这个,你可以创build任何你想要的哺乳动物/爬行动物/鸟类混合怪物的组合,现在你可以说它是“树皮! 同时飞出并伸出叉舌,它会妥善处理! 玩得开心!

我没有看到很多的inheritance点。

每当我在实际系统中使用inheritance时,我都被烧毁了,因为它导致了一个纠结的依赖关系networking,或者我只是及时地意识到,如果没有它,我会好得多。 现在,我尽可能避免它。 我根本就没有使用它。

 class Repeat: "Send a message more than once" def __init__(repeat, times, do): repeat.times = times repeat.do = do def __call__(repeat): for i in xrange(repeat.times): repeat.do() class Speak: def __init__(speak, animal): """ Check that the animal can speak. If not we can do something about it (eg ignore it). """ speak.__call__ = animal.speak def twice(speak): Repeat(2, speak)() class Dog: def speak(dog): print "Woof" class Cat: def speak(cat): print "Meow" >>> felix = Cat() >>> Speak(felix)() Meow >>> fido = Dog() >>> speak = Speak(fido) >>> speak() Woof >>> speak.twice() Woof >>> speak_twice = Repeat(2, Speak(felix)) >>> speak_twice() Meow Meow 

有一次詹姆斯·高斯林在新闻发布会上提出了一个问题:“如果你可以回去做不同的Java,你会留下什么?”。 他的回答是“类”,有笑声。 然而,他认真地解释说,真正的问题不在于阶级,而在于inheritance。

我认为它像一种药物依赖 – 它给你一个很好的解决scheme,感觉很好,但最终,它会把你搞砸。 由此我的意思是这是重用代码的一种方便的方式,但它强制孩子和父类之间的不健康的耦合。 对父母的改变可能会破坏孩子。 孩子依赖于父母的某些function,不能改变这个function。 因此,孩子提供的function也与父母相关 – 你只能拥有两者。

更好的做法是提供一个单一的面向类的客户端,用于实现接口的接口,使用在施工时组成的其他对象的function。 通过适当devise的接口来做到这一点,所有的耦合可以被消除,我们提供了一个高度可组合的API(这不是什么新东西 – 大多数程序员已经这样做,只是不够)。 请注意,实现类不能简单地公开function,否则客户端应该直接使用组合的类 – 它必须通过组合该function来做一些新的事情。

从inheritance阵营的观点来看,纯粹的代表团实施遭受了损失,因为他们需要大量的“粘合”方法,只是通过代表团“链”传递价值观。 但是,这只是重新devise一个使用委托的类似inheritance的devise。 对基于inheritance的devise曝光时间过长的程序员特别容易陷入陷阱,因为在没有意识到的情况下,他们会想到如何使用inheritance来实现某些东西,然后将其转换为委托。

像上面的代码一样正确的分离问题并不需要粘合方法,因为每一步实际上都是增加值 ,所以它们根本就不是“粘合”方法(如果它们没有增加值,那么devise是有缺陷的)。

这归结于:

  • 对于可重用的代码,每个类只应做一件事(做得好)。

  • inheritance创build的类不止一件事,因为它们与父类混淆在一起。

  • 因此,使用inheritance使得难以重用的类。

另一个小点是op的第三个例子,你不能调用isinstance()。 例如,将您的第3个示例传递给另一个需要的对象,并且“动物”键入一个调用。 如果你这样做,你不需要检查狗的types,猫的types,等等。 不确定实例检查是否真的是“Pythonic”,因为后期绑定。 但是,那么你将不得不实施某种方式,动物控制不会试图在卡车上扔芝士汉堡,因为芝士汉堡不会说话。

 class AnimalControl(object): def __init__(self): self._animalsInTruck=[] def catachAnimal(self,animal): if isinstance(animal,Animal): animal.speak() #It's upset so it speak's/maybe it should be makesNoise if not self._animalsInTruck.count <=10: self._animalsInTruck.append(animal) #It's then put in the truck. else: #make note of location, catch you later... else: return animal #It's not an Animal() type / maybe return False/0/"message" 

Python中的类基本上就是分组一堆函数和数据的方法。它们和C ++中的类是不同的。

我主要看到用于重写超类的方法的inheritance。 例如,也许更多Python'ishinheritance的使用将是..

 from world.animals import Dog class Cat(Dog): def speak(self): print "meow" 

当然,猫不是一种types的狗,但我有这个(第三方) Dog类完美的作品, 除了我想重写的speak方法 – 这节省了重新实现整个类,只是如此。 再一次,虽然Cat不是一种types的Dog ,但猫却inheritance了很多属性。

重写方法或属性的更好(实用)的例子是如何更改urllib的用户代理。 您基本上是urllib.FancyURLopener并更改版本属性( 从文档 ):

 import urllib class AppURLopener(urllib.FancyURLopener): version = "App/1.7" urllib._urlopener = AppURLopener() 

使用exception的另一种方式是exception,当以更“适当”的方式使用inheritance时:

 class AnimalError(Exception): pass class AnimalBrokenLegError(AnimalError): pass class AnimalSickError(AnimalError): pass 

然后,您可以捕获AnimalError以捕获从其inheritance的所有exception,或者像AnimalBrokenLegError