Python 中的继承有什么意义?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1020453/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
What’s the point of inheritance in Python?
提问by Stefano Borini
Suppose you have the following situation
假设你有以下情况
#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);
}
As you can see, makeSpeak is a routine that accepts a generic Animal object. In this case, Animal is quite similar to a Java interface, as it contains only a pure virtual method. makeSpeak does not know the nature of the Animal it gets passed. It just sends it the signal “speak” and leaves the late binding to take care of which method to call: either Cat::speak() or Dog::speak(). This means that, as far as makeSpeak is concerned, the knowledge of which subclass is actually passed is irrelevant.
如您所见,makeSpeak 是一个接受通用 Animal 对象的例程。在这种情况下,Animal 与 Java 接口非常相似,因为它只包含一个纯虚方法。makeSpeak 不知道它传递的 Animal 的性质。它只是向它发送信号“speak”,并让后期绑定处理调用哪个方法:Cat::speak() 或 Dog::speak()。这意味着,就 makeSpeak 而言,实际传递的是哪个子类的知识是无关紧要的。
But what about Python? Let's see the code for the same case in Python. Please note that I try to be as similar as possible to the C++ case for a moment:
但是 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)
Now, in this example you see the same strategy. You use inheritance to leverage the hierarchical concept of both Dogs and Cats being Animals. But in Python, there's no need for this hierarchy. This works equally well
现在,在此示例中,您将看到相同的策略。您可以使用继承来利用 Dogs 和 Cats 都是 Animals 的分层概念。但是在 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)
In Python you can send the signal “speak” to any object you want. If the object is able to deal with it, it will be executed, otherwise it will raise an exception. Suppose you add a class Airplane to both codes, and submit an Airplane object to makeSpeak. In the C++ case, it won't compile, as Airplane is not a derived class of Animal. In the Python case, it will raise an exception at runtime, which could even be an expected behavior.
在 Python 中,您可以将信号“说话”发送到您想要的任何对象。如果对象能够处理它,它将被执行,否则将引发异常。假设您在两个代码中都添加了一个类 Airplane,并向 makeSpeak 提交了一个 Airplane 对象。在 C++ 情况下,它不会编译,因为 Airplane 不是 Animal 的派生类。在 Python 的情况下,它会在运行时引发异常,这甚至可能是预期的行为。
On the other side, suppose you add a MouthOfTruth class with a method speak(). In the C++ case, either you will have to refactor your hierarchy, or you will have to define a different makeSpeak method to accept MouthOfTruth objects, or in java you could extract the behavior into a CanSpeakIface and implement the interface for each. There are many solutions...
另一方面,假设您添加了一个带有方法 speak() 的 MouthOfTruth 类。在 C++ 情况下,您要么必须重构层次结构,要么必须定义不同的 makeSpeak 方法来接受 MouthOfTruth 对象,或者在 Java 中,您可以将行为提取到 CanSpeakIface 并为每个对象实现接口。有很多解决方案...
What I'd like to point out is that I haven't found a single reason yet to use inheritance in Python (apart of frameworks and trees of exceptions, but I guess that alternative strategies exist). you don't need to implement a base-derived hierarchy to perform polymorphically. If you want to use inheritance to reuse implementation, you can accomplish the same through containment and delegation, with the added benefit that you can alter it at runtime, and you clearly define the interface of the contained, without risking unintended side effects.
我想指出的是,我还没有找到在 Python 中使用继承的单一原因(除了框架和异常树,但我想存在替代策略)。你不需要实现一个基派生的层次结构来执行多态。如果你想使用继承来重用实现,你可以通过包含和委托来完成同样的事情,额外的好处是你可以在运行时改变它,并且你清楚地定义了被包含的接口,而不会有意外副作用的风险。
So, in the end, the question stands: what's the point of inheritance in Python?
所以,最后,问题来了:Python 中的继承有什么意义?
Edit: thanks for the very interesting answers. Indeed you can use it for code reuse, but I am always careful when reusing implementation. In general, I tend to do very shallow inheritance trees or no tree at all, and if a functionality is common I refactor it out as a common module routine and then call it from each object. I do see the advantage of having one single point of change (eg. instead of adding to Dog, Cat, Moose and so on, I just add to Animal, which is the basic advantage of inheritance), but you can achieve the same with a delegation chain (eg. a la JavaScript). I'm not claiming it's better though, just another way.
编辑:感谢非常有趣的答案。的确,您可以将它用于代码重用,但我在重用实现时总是很小心。一般来说,我倾向于使用非常浅的继承树或根本没有树,如果一个功能是通用的,我会将它重构为一个通用的模块例程,然后从每个对象中调用它。我确实看到了单点更改的优势(例如,我没有添加到 Dog、Cat、Moose 等,而是添加到 Animal,这是继承的基本优势),但是您可以通过一个委托链(例如,a la JavaScript)。我并不是说它更好,只是另一种方式。
I also found a similar poston this regard.
我也在这方面找到了类似的帖子。
采纳答案by Roee Adler
You are referring to the run-time duck-typing as "overriding" inheritance, however I believe inheritance has its own merits as a design and implementation approach, being an integral part of object oriented design. In my humble opinion, the question of whether you can achieve something otherwise is not very relevant, because actually you could code Python without classes, functions and more, but the question is how well-designed, robust and readable your code will be.
您将运行时鸭子类型称为“覆盖”继承,但是我相信继承作为一种设计和实现方法有其自身的优点,是面向对象设计的一个组成部分。以我的拙见,你是否可以实现其他目标的问题并不是很重要,因为实际上你可以在没有类、函数等的情况下编写 Python,但问题是你的代码设计得如何、健壮和可读性如何。
I can give two examples for where inheritance is the right approach in my opinion, I'm sure there are more.
我可以举两个例子来说明在我看来继承是正确的方法,我相信还有更多。
First, if you code wisely, your makeSpeak function may want to validate that its input is indeed an Animal, and not only that "it can speak", in which case the most elegant method would be to use inheritance. Again, you can do it in other ways, but that's the beauty of object oriented design with inheritance - your code will "really" check whether the input is an "animal".
首先,如果您编写了明智的代码,您的 makeSpeak 函数可能想要验证它的输入确实是一个 Animal,而不仅仅是“它会说话”,在这种情况下,最优雅的方法是使用继承。同样,您可以通过其他方式做到这一点,但这就是具有继承性的面向对象设计的美妙之处——您的代码将“真正”检查输入是否为“动物”。
Second, and clearly more straightforward, is Encapsulation - another integral part of object oriented design. This becomes relevant when the ancestor has data members and/or non-abstract methods. Take the following silly example, in which the ancestor has a function (speak_twice) that invokes a then-abstract function:
其次,显然更直接的是封装——面向对象设计的另一个组成部分。当祖先具有数据成员和/或非抽象方法时,这变得相关。以下面这个愚蠢的例子为例,其中祖先有一个函数 (speak_twice) 来调用 then-abstract 函数:
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"
Assuming "speak_twice"
is an important feature, you don't want to code it in both Dog and Cat, and I'm sure you can extrapolate this example. Sure, you could implement a Python stand-alone function that will accept some duck-typed object, check whether it has a speak function and invoke it twice, but that's both non-elegant and misses point number 1 (validate it's an Animal). Even worse, and to strengthen the Encapsulation example, what if a member function in the descendant class wanted to use "speak_twice"
?
假设"speak_twice"
是一个重要的特性,你不想在 Dog 和 Cat 中都对它进行编码,我相信你可以推断出这个例子。当然,您可以实现一个 Python 独立函数,该函数将接受一些鸭子类型的对象,检查它是否具有 speak 函数并调用它两次,但这既不优雅又错过了第 1 点(验证它是一个动物)。更糟糕的是,为了加强封装示例,如果后代类中的成员函数想要使用"speak_twice"
?
It gets even clearer if the ancestor class has a data member, for example "number_of_legs"
that is used by non-abstract methods in the ancestor like "print_number_of_legs"
, but is initiated in the descendant class' constructor (e.g. Dog would initialize it with 4 whereas Snake would initialize it with 0).
如果祖先类有一个数据成员就更清楚了,例如"number_of_legs"
,它被祖先类中的非抽象方法使用"print_number_of_legs"
,但在后代类的构造函数中启动(例如,Dog 会用 4 初始化它,而 Snake 会初始化它与 0)。
Again, I'm sure there are endless more examples, but basically every (large enough) software that is based on solid object oriented design will require inheritance.
同样,我确信还有无数的例子,但基本上每个(足够大的)基于面向对象设计的软件都需要继承。
回答by Roberto Bonvallet
Inheritance in Python is all about code reuse. Factorize common functionality into a base class, and implement different functionality in the derived classes.
Python 中的继承都是关于代码重用的。将公共功能分解为基类,并在派生类中实现不同的功能。
回答by Jason Baker
Inheritance in Python is more of a convenience than anything else. I find that it's best used to provide a class with "default behavior."
Python 中的继承比其他任何东西都更方便。我发现最好用于提供具有“默认行为”的类。
Indeed, there is a significant community of Python devs who argue against using inheritance at all. Whatever you do, don't just don't overdo it. Having an overly complicated class hierarchy is a sure way to get labeled a "Java programmer", and you just can't have that. :-)
事实上,有一个重要的 Python 开发者社区反对使用继承。无论你做什么,不要只是不要过度。拥有一个过于复杂的类层次结构肯定会被贴上“Java 程序员”的标签,而你不能这样做。:-)
回答by bashmohandes
I think the point of inheritance in Python is not to make the code compile, it is for the real reason of inheritance which is extending the class into another child class, and to override the logic in the base class. However the duck typing in Python makes the "interface" concept useless, because you can just check if the method exist before invokation with no need to use an interface to limit the class structure.
我认为Python中继承的意义不是让代码编译,而是继承的真正原因是将类扩展到另一个子类中,并覆盖基类中的逻辑。然而,Python 中的鸭子类型使“接口”概念变得无用,因为您可以在调用之前检查该方法是否存在,而无需使用接口来限制类结构。
回答by David Cournapeau
I think that it is very difficult to give a meaningful, concrete answer with such abstract examples...
我觉得用这么抽象的例子很难给出一个有意义的、具体的答案......
To simplify, there are two types of inheritance: interface and implementation. If you need to inherit the implementation, then python is not so different than statically typed OO languages like C++.
为简化起见,有两种类型的继承:接口和实现。如果您需要继承实现,那么 python 与静态类型的 OO 语言(如 C++)没有太大区别。
Inheritance of interface is where there is a big difference, with fundamental consequences for the design of your software in my experience. Languages like Python does not force you to use inheritance in that case, and avoiding inheritance is a good point in most cases, because it is very hard to fix a wrong design choice there later. That's a well known point raised in any good OOP book.
接口的继承是有很大区别的地方,根据我的经验,它对您的软件设计产生根本性的影响。在这种情况下,像 Python 这样的语言不会强迫您使用继承,并且在大多数情况下避免继承是一个好点,因为以后很难在那里修复错误的设计选择。这是任何一本好的 OOP 书籍都提出的众所周知的观点。
There are cases where using inheritance for interfaces is advisable in Python, for example for plug-ins, etc... For those cases, Python 2.5 and below lacks a "built-in" elegant approach, and several big frameworks designed their own solutions (zope, trac, twister). Python 2.6 and above has ABC classes to solve this.
有一些情况在Python中建议对接口使用继承,例如插件等......对于这些情况,Python 2.5及以下缺乏“内置”的优雅方法,几个大框架设计了自己的解决方案(zope,trac,twister)。Python 2.6 及更高版本有ABC 类来解决这个问题。
回答by Larry Lustig
It's not inheritance that duck-typing makes pointless, it's interfaces — like the one you chose in creating an all abstract animal class.
鸭子类型没有意义的不是继承,而是接口——就像你在创建一个完全抽象的动物类时选择的接口一样。
If you had used an animal class that introduce some real behavior for its descendants to make use of, then dog and cat classes that introduced some additional behavior there would be a reason for both classes. It's only in the case of the ancestor class contributing no actual code to the descendant classes that your argument is correct.
如果您使用的动物类为其后代引入了一些真实的行为以供使用,那么引入一些额外行为的狗和猫类将有两个类的原因。只有在祖先类没有为后代类贡献实际代码的情况下,您的论点才是正确的。
Because Python can directly know the capabilities of any object, and because those capabilities are mutable beyond the class definition, the idea of using a pure abstract interface to "tell" the program what methods can be called is somewhat pointless. But that's not the sole, or even the main, point of inheritance.
因为 Python 可以直接知道任何对象的能力,并且因为这些能力在类定义之外是可变的,所以使用纯抽象接口来“告诉”程序可以调用哪些方法的想法有些毫无意义。但这不是唯一的,甚至不是主要的继承点。
回答by agileotter
In C++/Java/etc, polymorphism is caused by inheritance. Abandon that misbegotten belief, and dynamic languages open up to you.
在 C++/Java/etc 中,多态是由继承引起的。放弃那个错误的信念,动态语言向你敞开心扉。
Essentially, in Python there is no interface so much as "the understanding that certain methods are callable". Pretty hand-wavy and academic-sounding, no? It means that because you call "speak" you clearly expect that the object should have a "speak" method. Simple, huh? This is very Liskov-ian in that the users of a class define its interface, a good design concept that leads you into healthier TDD.
本质上,在 Python 中没有像“理解某些方法是可调用的”那样的接口。漂亮的手波浪和学术听起来,不是吗?这意味着因为您调用“speak”,您显然希望该对象应该具有“speak”方法。很简单吧?这是非常 Liskov 式的,因为类的用户定义了它的接口,这是一个很好的设计概念,可以引导您进入更健康的 TDD。
So what is left is, as another poster politely managed to avoid saying, a code sharing trick. You could write the same behavior into each "child" class, but that would be redundant. Easier to inherit or mix-in functionality that is invariant across the inheritance hierarchy. Smaller, DRY-er code is better in general.
所以剩下的就是,正如另一位发帖人礼貌地设法避免说的那样,一个代码共享技巧。您可以将相同的行为写入每个“子”类,但这将是多余的。更容易继承或混合跨继承层次结构不变的功能。一般来说,更小、更干燥的代码更好。
回答by mandroid
You can get around inheritance in Python and pretty much any other language. It's all about code reuse and code simplification though.
您可以在 Python 和几乎任何其他语言中解决继承问题。不过,这都是关于代码重用和代码简化的。
Just a semantic trick, but after building your classes and base classes, you don't even have to know what's possible with your object to see if you can do it.
只是一个语义技巧,但是在构建类和基类之后,您甚至不必知道对象有什么可能就可以查看是否可以做到。
Say you have d which is a Dog that subclassed Animal.
假设您有 d,它是将 Animal 子类化的 Dog。
command = raw_input("What do you want the dog to do?")
if command in dir(d): getattr(d,command)()
If whatever the user typed in is available, the code will run the proper method.
如果用户输入的任何内容可用,代码将运行正确的方法。
Using this you can create whatever combination of Mammal/Reptile/Bird hybrid monstrosity you want, and now you can make it say 'Bark!' while flying and sticking out its forked tongue and it will handle it properly! Have fun with it!
使用它你可以创建任何你想要的哺乳动物/爬行动物/鸟类混合怪物的组合,现在你可以让它说“树皮!” 一边飞一边伸出分叉的舌头,它会妥善处理!玩得开心!
回答by Mike A
I don't see much point in inheritance.
我认为继承没有多大意义。
Every time I have ever used inheritance in real systems, I got burned because it led to a tangled web of dependencies, or I simply realised in time that I would be a lot better off without it. Now, I avoid it as much as possible. I simply never have a use for it.
每次我在实际系统中使用继承时,我都会被烧毁,因为它导致了一个复杂的依赖关系网,或者我只是及时意识到没有它我会过得更好。现在,我尽可能地避免它。我从来没有用过它。
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 (e.g. 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
James Gosling was once asked at a press conference a question along the lines: "If you could go back and do Java differently, what would you leave out?". His response was "Classes", to which there was laughter. However, he was serious and explained that really, it was not classes that were the problem but inheritance.
James Gosling 曾经在新闻发布会上被问到一个类似的问题:“如果你能回去以不同的方式做 Java,你会遗漏什么?”。他的回答是“上课”,对此有笑声。然而,他很认真地解释说,实际上,问题不是类,而是继承。
I kind of view it like a drug dependency - it gives you a quick fix that feels good, but in the end, it messes you up. By that I mean that it is a convenient way to reuse code, but it forces an unhealthy coupling between child and parent class. Changes to the parent may break the child. The child is dependant on the parent for certain functionality and cannot alter that functionality. Therefore the functionality provided by the child is also tied to the parent - you can only have both.
我有点把它看成一种药物依赖——它给你一个感觉很好的快速修复,但最终,它把你搞砸了。我的意思是,这是一种重用代码的便捷方式,但它会在子类和父类之间造成不健康的耦合。对父项的更改可能会破坏子项。孩子的某些功能依赖于父母,并且不能改变该功能。因此,子项提供的功能也与父项相关联 - 您只能同时拥有两者。
Better is to provide one single client facing class for an interface which implements the interface, using the functionality of other objects which are composed at construction time. Doing this via properly designed interfaces, all coupling can be eliminated and we provide a highly composable API (This is nothing new - most programmers already do this, just not enough). Note that the implementing class must not simply expose functionality, otherwise the client should just use the composed classes directly - it mustdo something new by combining that functionality.
更好的是为实现该接口的接口提供一个面向客户端的类,使用在构造时组合的其他对象的功能。通过正确设计的接口做到这一点,所有耦合都可以消除,我们提供了一个高度可组合的 API(这不是什么新鲜事 - 大多数程序员已经这样做了,只是还不够)。请注意,实现类不能简单地公开功能,否则客户端应该直接使用组合类 - 它必须通过组合该功能来做一些新的事情。
There is the argument from the inheritance camp that pure delegation implementations suffer because they require lots of 'glue' methods which simply pass along values through a delegation 'chain'. However, this is simply reinventing an inheritance-like design using delegation. Programmers with too many years of exposure to inheritance-based designs are particularly vulnerable to falling into this trap, as, without realising it, they will think of how they would implement something using inheritance and then convert that to delegation.
继承阵营认为纯委托实现会受到影响,因为它们需要大量“粘合”方法,这些方法只是通过委托“链”传递值。然而,这只是使用委托重新发明类继承的设计。在基于继承的设计中接触过多年的程序员特别容易掉入这个陷阱,因为在没有意识到这一点的情况下,他们会考虑如何使用继承来实现某些东西,然后将其转换为委托。
Proper separation of concerns like the above code doesn't require glue methods, as each step is actually adding value, so they are not really 'glue' methods at all (if they don't add value, the design is flawed).
像上面的代码那样正确分离关注点不需要粘合方法,因为每一步实际上都是在增加价值,所以它们根本不是真正的“粘合”方法(如果它们不增加价值,设计就有缺陷)。
It boils down to this:
它归结为:
For reusable code, each class should do only one thing (and do it well).
Inheritance creates classes that do more than one thing, because they are mixed up with parent classes.
Therefore, using inheritance makes classes that are hard to reuse.
对于可重用代码,每个类应该只做一件事(并且做好)。
继承创建了做不止一件事的类,因为它们与父类混合在一起。
因此,使用继承会使类难以重用。
回答by yedevtxt
Another small point is that op's 3'rd example, you can't call isinstance(). For example passing your 3'rd example to another object that takes and "Animal" type an calls speak on it. If you do it don't you would have to check for dog type, cat type, and so on. Not sure if instance checking is really "Pythonic", because of late binding. But then you would have to implement some way that the AnimalControl doesn't try to throw Cheeseburger types in the truck, becuase Cheeseburgers don't speak.
另一个小点是 op 的第三个例子,你不能调用 isinstance()。例如,将您的第三个示例传递给另一个对象,该对象采用“动物”类型,并在其上进行通话。如果不这样做,则必须检查狗的类型、猫的类型等。由于后期绑定,不确定实例检查是否真的是“Pythonic”。但是,您将不得不实施某种方式,使 AnimalControl 不会尝试将 Cheeseburger 类型扔到卡车中,因为 Cheeseburgers 不会说话。
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"