继承在 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);
}

正如您所看到的,makspeak 是一个接受泛型 Animal 对象的例程。在本例中,Animal 非常类似于 Java 接口,因为它只包含一个纯虚方法。MakeTalk 不知道它所传递的动物的性质。它只是向它发送信号“ speak”,并留下后期绑定来处理要调用哪个方法: Cat: : speak ()或 Dog: : speak ()。这意味着,就 makeTalk 而言,实际传递哪个子类的知识是不相关的。

那巨蟒呢?让我们看看 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 中,您可以将信号“ speak”发送到任何您想要的对象。如果对象能够处理它,它将被执行,否则将引发异常。假设您在两个代码中都添加了一个类 Airplane,并提交了一个 AirFlight 对象来 make Talk。在 C + + 的情况下,它不会编译,因为 Aircraft 不是 Animal 的派生类。在 Python 情况下,它将在运行时引发异常,这甚至可能是预期的行为。

另一方面,假设您添加了一个 MouthOfTruth 类和一个 speak ()方法。在 C + + 的例子中,要么你必须重构你的层次结构,要么你必须定义一个不同的 makTalk 方法来接受 MouthOfTruth 对象,或者在 java 中你可以把行为提取到一个 CanSpikIface 中并为每个对象实现接口。有很多解决办法。

我想指出的是,我还没有找到在 Python 中使用继承的单一原因(除了框架和异常树之外,但是我猜还有其他策略存在)。您不需要实现基本派生的层次结构来执行多态性。如果您希望使用继承来重用实现,那么您可以通过包含和委托来实现同样的功能,还有一个额外的好处,那就是您可以在运行时修改它,并且您可以清楚地定义包含的接口,而不会产生意想不到的副作用。

因此,最后,问题仍然存在: 在 Python 中继承的意义是什么?

编辑 : 感谢您非常有趣的回答。实际上,您可以将它用于代码重用,但是在重用实现时我总是很小心。一般来说,我倾向于做非常浅的继承树,或者根本不做继承树,如果一个功能是通用的,我将它重构为一个通用的模块例程,然后从每个对象调用它。我确实看到了单点更改的好处(例如,不添加 Dog、 Cat、 Moose 等,而是添加 Animal,这是继承的基本优势) ,但是你可以通过代理链(例如,la JavaScript)实现同样的效果。我不是说这样更好,只是换种方式。

在这方面我也找到了 类似的职位

21942 次浏览

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.

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.

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".

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:

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"?

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).

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.

Classes in Python are basically just ways of grouping a bunch of functions and data.. They are different to classes in C++ and such..

I've mostly seen inheritance used for overriding methods of the super-class. For example, perhaps a more Python'ish use of inheritance would be..

from world.animals import Dog


class Cat(Dog):
def speak(self):
print "meow"

Of course cats aren't a type of dog, but I have this (third party) Dog class which works perfectly, except the speak method which I want to override - this saves re-implementing the entire class, just so it meows. Again, while Cat isn't a type of Dog, but a cat does inherit a lot of attributes..

A much better (practical) example of overriding a method or attribute is how you change the user-agent for urllib. You basically subclass urllib.FancyURLopener and change the version attribute (from the documentation):

import urllib


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


urllib._urlopener = AppURLopener()

Another manner exceptions are used is for Exceptions, when inheritance is used in a more "proper" way:

class AnimalError(Exception):
pass


class AnimalBrokenLegError(AnimalError):
pass


class AnimalSickError(AnimalError):
pass

..you can then catch AnimalError to catch all exceptions which inherit from it, or a specific one like AnimalBrokenLegError

In C++/Java/etc, polymorphism is caused by inheritance. Abandon that misbegotten belief, and dynamic languages open up to you.

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.

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.

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++.

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.

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.

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."

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. :-)

Inheritance in Python is all about code reuse. Factorize common functionality into a base class, and implement different functionality in the derived classes.

You can get around inheritance in Python and pretty much any other language. It's all about code reuse and code simplification though.

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.

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!

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.

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 must do something new by combining that functionality.

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.

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.

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.

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"