80 votes

Quel est l'intérêt de l'héritage en Python?

Je m'excuse si cette question est longue. Cela faisait partie d'un post de blog je l'ai fait il y a quelques temps, et un lecteur m'a proposé de le publier sur stackoverflow. J'ai coupé un peu.

Supposons que vous avez la situation suivante

#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);
}

Comme vous pouvez le voir, makeSpeak est une routine qui accepte un générique Animal objet. Dans ce cas, l'Animal est assez similaire à une interface Java, comme elle ne contient qu'une méthode virtuelle pure. makeSpeak ne connaît pas la nature de l'Animal, elle est adoptée. Il envoie le signal "parler" et laisse la liaison tardive pour prendre soin de la méthode à appeler: Cat::parler() ou Chien::parler(). Cela signifie que, dans la mesure du makeSpeak est concerné, la connaissance de ce qui sous-classe est réellement passé n'est pas pertinent.

Mais qu'en Python? Nous allons voir le code pour le même cas en Python. Veuillez noter que j'essaie d'être aussi semblable que possible à la C++ cas pour un moment:

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)

Maintenant, dans cet exemple, vous voyez la même stratégie. Vous utiliser l'héritage pour tirer parti de la conception hiérarchique de les Chiens et les Chats étant des Animaux. Mais en Python, il n'y a pas besoin de cette hiérarchie. Ceci fonctionne aussi bien

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)

En Python, vous pouvez envoyer le signal "parler" à n'importe quel objet que vous voulez. Si l'objet est en mesure de traiter avec elle, il sera exécuté, sinon il va lever une exception. Supposez que vous ajoutez une classe d'Avion pour les deux codes, et de soumettre un Avion de l'objet à makeSpeak. Dans le C++ cas, il ne compile pas, comme l'Avion n'est pas une classe dérivée de l'Animal. Dans le Python cas, il déclenche une exception lors de l'exécution, qui pourrait même être un comportement attendu.

De l'autre côté, supposons que vous ajoutez un MouthOfTruth classe avec une méthode parle(). Dans le C++, vous aurez à refactoriser votre hiérarchie, ou vous aurez à définir une autre makeSpeak méthode d'accepter MouthOfTruth objets, ou en java, vous pourrez extraire le comportement dans un CanSpeakIface et de mettre en œuvre l'interface pour chaque. Il existe beaucoup de solutions...

Ce que je voudrais souligner, c'est que je n'ai pas trouvé une seule raison encore utiliser l'héritage en Python (à l'exception des cadres et des arbres des exceptions, mais je suppose que les stratégies alternatives existent). vous n'avez pas besoin de mettre en œuvre une base de dérivés de la hiérarchie pour effectuer polymorphically. Si vous souhaitez utiliser l'héritage pour la réutilisation de la mise en œuvre, vous pouvez faire la même chose par le biais de confinement et de délégation, avec l'avantage supplémentaire que vous pouvez modifier au moment de l'exécution, et vous avez clairement définir l'interface de l'contenus, sans risquer de provoquer des effets secondaires indésirables.

Donc, en fin de compte, la question est: quel est le point de succession dans la de Python?

Edit: merci pour les réponses très intéressantes. En effet, vous pouvez l'utiliser pour la réutilisation de code, mais je suis toujours prudent lors de la réutilisation de mise en œuvre. En général, j'ai tendance à faire très peu profonde de l'héritage des arbres ou pas d'arbre, et si une fonctionnalité est bon je refactoriser un module commun de routine et ensuite appeler à partir de chaque objet. Je vois l'avantage d'avoir un seul point de changement (par exemple. au lieu d'ajouter de Chien, de Chat, de l'Orignal et ainsi de suite, je viens de l'ajouter à l'Animal, qui est le principal avantage de l'héritage), mais vous pouvez obtenir la même chose avec une délégation de la chaîne (par exemple. la JavaScript). Je ne dis pas que c'est mieux si, juste une autre façon.

J'ai aussi trouvé un poste similaire sur ce sujet.

78voto

Roee Adler Points 10146

Vous faites référence à l'exécution de canard-taper comme "primordial" de l'héritage, cependant je crois que l'héritage a ses propres mérites que la conception et la mise en œuvre de l'approche, qui fait partie intégrante de la conception orientée objet. À mon humble avis, la question de savoir si vous pouvez obtenir quelque chose, sinon, n'est pas très pertinent, parce qu'en fait, vous pourriez code Python sans classes, fonctions et plus, mais la question est de savoir comment bien conçu, robuste et lisible de votre code.

Je peux donner deux exemples où l'héritage est la bonne approche, à mon avis, je suis sûr qu'il ya plus.

Tout d'abord, si vous le code à bon escient, votre makeSpeak fonction peut vouloir valider que son entrée est en effet un Animal, et pas seulement qu'il "parle", auquel cas les plus élégants de la méthode serait d'utiliser l'héritage. Encore une fois, vous pouvez le faire par d'autres moyens, mais c'est la beauté de la conception orientée objet avec l'héritage - votre code sera "vraiment" vérifier si l'entrée est un "animal".

Deuxièmement, et clairement plus simple, est l'Encapsulation - une autre partie intégrante de la conception orientée objet. C'est important lorsque l'ancêtre de données de membres et/ou non-des méthodes abstraites. Prendre exemple stupide, dont l'ancêtre a une fonction (speak_twice) qui appelle une fonction abstraite:

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"

En supposant "speak_twice" est un élément important, vous ne voulez pas de code dans les deux Chiens et les Chats, et je suis sûr que vous pouvez en tirer de cet exemple. Bien sûr, vous pourriez mettre en œuvre un Python autonome de la fonction qui va accepter de certains de canard tapé par l'objet, vérifier si il a un parler de la fonction et de l'appeler deux fois, mais c'est à la fois non-elégant et manque point numéro 1 (valider c'est un Animal). Pire encore, et à renforcer l'Encapsulation exemple, si une fonction membre de la classe descendante voulu utiliser "speak_twice"?

Il devient encore plus clair si l'ancêtre de la classe a un membre de données, par exemple "number_of_legs" qui est utilisée par les non-méthodes abstraites dans l'ancêtre, comme "print_number_of_legs", mais qui est initiée dans le descendant de la classe de constructeur (par exemple, le Chien serait l'initialiser avec 4, tandis que le Serpent serait l'initialiser à 0).

Encore une fois, je suis sûr qu'il y a une infinité d'autres exemples, mais, fondamentalement, tous les (assez grand) un logiciel qui est basé sur de solides de conception orientée objet nécessitera l'héritage.

12voto

Roberto Bonvallet Points 6336

L’héritage en Python est tout au sujet de réutilisation du code. Factoriser des fonctionnalités communes dans une classe de base et mettre en œuvre différentes fonctionnalités dans les classes dérivées.

10voto

Jason Baker Points 56682

Héritage en Python est plus une commodité qu’autre chose. Je trouve qu’il est mieux utilisé pour fournir une classe avec « comportement par défaut. »

En effet, il y a une importante communauté de développeurs Python qui plaident contre l’héritage du tout. Tout ce que vous faites, ne faites pas juste n’exagérez pas. Avoir une hiérarchie de classes trop compliqué est un moyen sûr d’obtenir étiquetés « programmeur Java », et il ne suffit que. :-)

8voto

bashmohandes Points 1930

Je pense que le point d’héritage en Python est ne pas de faire le code à compiler, c’est la raison réelle de l’héritage qui étend la classe dans une autre classe de l’enfant et pour substituer les logiques dans la classe de base. Cependant le duck-typing dans Python rend le concept de « interface » inutile, car vous pouvez simplement vérifier si la méthode existe avant l’invocation sans avoir besoin d’utiliser une interface pour limiter la structure de classe.

7voto

David Cournapeau Points 21956

Je pense qu'il est très difficile de donner un sens, de réponses concrètes avec abstraits tels exemples...

Pour simplifier, il existe deux types d'héritage: l'interface et la mise en œuvre. Si vous avez besoin d'hériter de la mise en œuvre, puis de python n'est pas si différente de la statiquement typé OO langages tels que le C++.

L'héritage de l'interface est où il y a une grande différence, avec des conséquences fondamentales pour la conception de votre logiciel dans mon expérience. Des langages comme Python ne pas vous forcer à utiliser l'héritage dans ce cas, et en évitant l'héritage est un bon point dans la plupart des cas, car il est très difficile de fixer un mauvais choix de conception plus tard. C'est bien connu point soulevé dans toute bonne programmation orientée objet livre.

Il y a des cas où l'utilisation de l'héritage pour les interfaces est conseillé en Python, par exemple pour les plugins, etc... dans ces cas, Python 2.5 et au-dessous de manque un "built-in" approche élégante, et plusieurs grands cadres ont conçu leurs propres solutions (zope, trac, twister). Python 2.6 et ci-dessus a ABC classes pour résoudre ce.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X