Quel est le but de l'héritage en Python?

supposons que vous ayez 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 objet Animal Générique. Dans ce cas, Animal est tout à fait similaire à une interface Java, car il ne contient qu'une méthode purement virtuelle. makeSpeak ne connaît pas la nature de l'Animal qu'il passe. Il lui envoie simplement le signal "speak" et laisse la reliure tardive pour prendre soin de quelle méthode appeler: soit Cat::speak() ou Dog::speak(). Cela signifie que, en ce qui concerne makeSpeak, la connaissance de la sous-classe qui est effectivement transmise n'est pas pertinente.

mais Qu'en est-il de Python? Voyons le code pour le même cas en Python. S'il vous plaît noter que j'essaie d'être aussi similaire que possible au cas C++ 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 utilisez l'héritage pour tirer parti du concept hiérarchique selon lequel les chiens et les chats sont des animaux. Mais en Python, il n'y a pas besoin pour cette hiérarchie. Cela fonctionne également 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" speak " à n'importe quel objet que vous voulez. Si l'objet est capable de le traiter, il sera exécuté, sinon il soulèvera une exception. Supposons que vous ajoutiez un avion de classe aux deux codes, et soumettiez un objet avion à makeSpeak. Dans le cas du C++, il ne compilera pas, car Airplane n'est pas une classe dérivée D'Animal. Dans le cas de Python, il va soulever une exception à l'exécution, qui pourrait même un comportement attendu.

de l'autre côté, supposons que vous ajoutiez une classe MouthOfTruth avec une méthode speak(). Dans le cas du C++, soit vous devrez reformuler votre hiérarchie, soit vous devrez définir une méthode makeSpeak différente pour accepter les objets de la MouthOfTruth, ou en java vous pourriez extraire le comportement dans un CanSpeakIface et implémenter l'interface pour chacun. Il existe de nombreuses solutions...

ce que je voudrais souligner est que je n'ai pas trouvé une seule raison pour utiliser l'héritage en Python (à part les cadres et les arbres d'exceptions, mais je suppose que d'autres stratégies existent). vous n'avez pas besoin de mettre en œuvre une hiérarchie dérivée de la base pour effectuer polymorphiquement. Si vous voulez utiliser l'héritage pour réutiliser la mise en œuvre, vous pouvez accomplir la même chose par le confinement et la délégation, avec l'avantage supplémentaire que vous pouvez le modifier à l'exécution, et vous définissez clairement l'interface du contenu, sans risque d'effets secondaires involontaires.

donc, à la fin, la question se pose: à quoi sert l'héritage en Python?

Modifier : 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 la mise en œuvre. En général, j'ai tendance à faire des arbres héréditaires très peu profonds ou aucun arbre du tout, et si une fonctionnalité est commune, je la reformule comme une routine de module commune et puis l'appelle de chaque objet. Je ne vois l'avantage d'avoir un seul point de changement (par exemple. au lieu d'ajouter à Chien, Chat, orignal et ainsi de suite, je viens d'ajouter à Animal, qui est l'avantage de base de l'héritage), mais vous pouvez obtenir la même chose avec une chaîne de délégation (par exemple. a la JavaScript). Je ne dis pas que c'est mieux, juste une autre façon.

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

76
demandé sur bignose 2009-06-20 03:28:18

11 réponses

vous faites référence à l'exécution-time duck-typing comme héritage" supérieur", mais je crois que l'héritage a ses propres mérites comme une approche de conception et de mise en œuvre, étant une partie intégrante de la conception orientée objet. À mon humble avis, la question de savoir si vous pouvez réaliser quelque chose d'autre n'est pas très pertinente, parce qu'en fait vous pourriez coder Python sans classes, fonctions et plus, mais la question Est de savoir comment votre code sera bien conçu, robuste et lisible.

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 codez sagement, votre fonction makeSpeak peut vouloir valider que son entrée est bien un Animal, et pas seulement que "il peut parler", auquel cas la méthode la plus élégante serait d'utiliser l'héritage. Encore une fois, vous pouvez le faire d'une autre manière, mais c'est la beauté du design orienté objet avec héritage - votre code va" vraiment " vérifier si l'entrée est un "animal".

Deuxièmement, et clairement plus simple, est Encapsulation - une autre partie intégrante de la conception orientée objet. Cela devient pertinent lorsque l'ancêtre possède des données et/ou des méthodes non 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 que "speak_twice" est une caractéristique importante, vous ne voulez pas la coder en chien et Chat, et je suis sûr que vous pouvez extrapoler cet exemple. Bien sûr, vous pouvez implémenter une fonction Python autonome qui acceptera un objet dactylographié en duck, vérifier s'il a une fonction speak et l'invoquer deux fois, mais c'est à la fois non élégant et manque le point numéro 1 (valider C'est un Animal). Pire encore, et pour renforcer l'exemple D'Encapsulation, que se passerait-il si un membre de la classe descendante voulait utiliser "speak_twice" ?

il devient encore plus clair si le la classe ancêtre a un membre de données, par exemple "number_of_legs" qui est utilisé par des méthodes non abstraites dans l'ancêtre comme "print_number_of_legs" , mais qui est initié dans le constructeur de la classe descendant (Par exemple Dog l'initialiserait avec 4 alors que Snake l'initialiserait avec 0).

encore une fois, je suis sûr qu'il y a d'innombrables autres exemples, mais fondamentalement chaque (assez grand) logiciel qui est basé sur la conception orientée objet solide exigera l'héritage.

79
répondu Roee Adler 2009-08-04 14:50:46

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

12
répondu Roberto Bonvallet 2009-06-20 04:51:10

L'héritage en Python est plus pratique 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 communauté significative de devs Python qui s'opposent à l'usage de l'héritage. Quoi que tu fasses, n'en fais pas trop. Avoir une hiérarchie de classe trop compliquée est une façon sûre d'obtenir étiqueté un "programmeur Java", et vous ne pouvez tout simplement pas avoir cela. :- )

9
répondu Jason Baker 2009-06-20 03:44:11

je pense que le but de l'héritage en Python n'est pas de faire compiler le code, c'est pour la vraie raison de l'héritage qui étend la classe dans une autre classe enfant, et pour outrepasser la logique dans la classe de base. Cependant, le fait de taper duck en Python rend le concept "interface" inutile, car vous pouvez simplement vérifier si la méthode existe avant invokation sans avoir besoin d'utiliser une interface pour limiter la structure de la classe.

8
répondu bashmohandes 2009-10-15 22:11:13

je pense qu'il est très difficile de donner une réponse concrète et significative avec de tels exemples abstraits...

pour simplifier, il existe deux types d'héritage: l'interface et la mise en œuvre. Si vous devez hériter de l'implémentation, alors python n'est pas si différent que des langages OO statiquement typés comme C++.

héritage d'interface est là où il ya une grande différence, avec des conséquences fondamentales pour la conception de votre logiciel en de mon expérience. Les langages comme Python ne vous forcent pas à utiliser l'héritage dans ce cas, et éviter l'héritage est un bon point dans la plupart des cas, parce qu'il est très difficile de fixer un mauvais choix de conception là-bas plus tard. C'est un point bien connu soulevé dans n'importe quel bon livre.

il y a des cas où l'utilisation de l'héritage pour les interfaces est conseillée en Python, par exemple pour les plug-ins, etc... Pour ces cas, Python 2.5 et inférieur manque d'une approche" intégrée " élégante, et plusieurs grands frameworks ont conçu leurs propres solutions (zope, trac, twister). Python 2.6 et au-dessus a classes ABC pour résoudre ce .

7
répondu David Cournapeau 2009-10-15 22:10:37

En C++/Java/etc, le polymorphisme est causée par l'héritage. Abandonnez cette croyance malencontreuse, et des langues dynamiques s'ouvrent à vous.

Essentiellement, en Python il n'y a pas d'interface tellement que "la compréhension que certaines méthodes sont remboursables". Assez attrayante et universitaire de sondage, non? Cela signifie que parce que vous appelez " parler "vous attendez clairement que l'objet devrait avoir une méthode" parler". Simple, hein? C'est très Liskov-ian en ce que les utilisateurs d'une classe définir son interface, un bon concept de conception qui vous mène à une meilleure santé TDD.

donc ce qui reste est, comme un autre poster poliment réussi à éviter de dire, un truc de partage de code. Vous pourriez écrire le même comportement dans chaque classe "enfant", mais ce serait redondant. Plus facile d'hériter ou de mélanger-dans la fonctionnalité qui est invariant à travers la hiérarchie de l'héritage. Plus petit, le code plus sec est meilleur en général.

5
répondu agileotter 2009-10-15 22:10:48

ce n'est pas un héritage que de taper du canard rend inutile, ce sont des interfaces-comme celle que vous avez choisie pour créer une classe animale abstraite.

si vous aviez utilisé une classe animale qui introduisait un comportement réel pour ses descendants, alors des classes de chiens et de chats qui introduisaient un comportement supplémentaire, il y aurait une raison pour les deux classes. C'est seulement dans le cas de la classe ancêtre ne fournissant aucun code réel aux classes descendantes que votre l'argument est correct.

parce que Python peut connaître directement les capacités de n'importe quel objet, et parce que ces capacités sont mutables au-delà de la définition de classe, l'idée d'utiliser une interface abstraite pure pour "dire" au programme quelles méthodes peuvent être appelées est quelque peu inutile. Mais ce n'est pas le seul, ni même le principal, point d'héritage.

5
répondu Larry Lustig 2009-10-15 22:35:48

vous pouvez contourner l'héritage en Python et à peu près n'importe quelle autre langue. Il s'agit de réutiliser le code et de le simplifier.

c'est juste un truc sémantique, mais après avoir construit vos classes et vos classes de base, vous n'avez même pas besoin de savoir ce qui est possible avec votre objet pour voir si vous pouvez le faire.

dites que vous avez d qui est un chien qui sous-classe Animal.

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

si ce que l'Utilisateur a saisi est disponible, le code exécutera la méthode appropriée.

en utilisant ceci vous pouvez créer n'importe quelle combinaison de monstre Hybride mammifère/Reptile/oiseau que vous voulez, et maintenant vous pouvez le faire dire ' Bark!"tout en volant et sortir sa langue fourchue et il le manipulera correctement! Avoir du plaisir avec elle!

1
répondu mandroid 2009-06-24 23:40:17

Je ne vois pas l'intérêt d'hériter.

chaque fois que j'ai utilisé l'héritage dans des systèmes réels, j'ai été grillé parce qu'il conduisait à un enchevêtrement de dépendances, ou j'ai tout simplement réalisé à temps que je serais bien mieux sans. Maintenant, j'évite autant que possible. J'ai tout simplement jamais l'utiliser.

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 s'est fait poser une question à la conférence de presse: "si tu pouvais revenir en arrière et faire Java différemment, que voulez-vous quitter?". Sa réponse fut "Classes", pour lesquelles il y eut des rires. Cependant, il était sérieux et a expliqué que vraiment, ce n'était pas les classes qui étaient le problème mais l'héritage.

je l'ai vue comme une dépendance à la drogue - il vous donne une solution rapide qui sent bon, mais à la fin, il mess. Je veux dire par là que c'est un moyen pratique de réutiliser du code, mais cela force un couplage malsain entre la classe de l'enfant et la classe du parent. Des modifications à la le parent peut briser l'enfant. L'enfant dépend du parent pour certaines fonctionnalités et ne peut pas les modifier. Par conséquent, la fonctionnalité fournie par l'enfant est également lié à la mère - vous ne pouvez avoir les deux.

mieux est de fournir une classe de face client unique pour une interface qui implémente l'interface, en utilisant la fonctionnalité d'autres objets qui sont composés au moment de la construction. Ceci via des interfaces correctement conçues, tous les couplages peut être éliminé et nous fournissons une API hautement composable (ce n'est pas nouveau - la plupart des programmeurs le font déjà, mais pas assez). Notez que la classe d'implémentation ne doit pas simplement exposer la fonctionnalité, sinon le client doit juste utiliser les classes composées directement - il doit faire quelque chose de nouveau en combinant cette fonctionnalité.

il y a l'argument du camp de l'héritage que la mise en œuvre pure délégation souffre parce qu'ils exigent beaucoup de méthodes de "colle" qui ne font que transmettre des valeurs à travers une "chaîne" de délégation. Toutefois, il s'agit tout simplement de réinventer une conception de type héritage en utilisant la délégation. Les programmeurs qui ont trop d'années d'exposition aux conceptions basées sur l'héritage sont particulièrement vulnérables à tomber dans ce piège, car, sans s'en rendre compte, ils penseront à la façon dont ils mettraient en œuvre quelque chose en utilisant l'héritage et puis convertir cela en délégation.

séparation correcte des préoccupations comme le le code ci-dessus ne nécessite pas de méthodes de colle, car chaque étape est en fait ajouter de la valeur , de sorte qu'ils ne sont pas vraiment des méthodes de "colle" du tout (si elles ne ajoutent pas de valeur, la conception est défectueuse).

il se résume à ceci:

  • pour le code réutilisable, chaque classe ne doit faire qu'une chose (et le font bien).

  • L'héritage crée des classes qui font plus d'une chose, parce qu'ils sont mélangé avec les cours des parents.

  • par conséquent, l'utilisation de l'héritage rend les classes qui sont difficiles à réutiliser.

1
répondu Mike A 2009-07-01 20:55:23

un Autre petit point, c'est que l'op s 3'rd exemple, vous ne pouvez pas appeler isinstance(). Par exemple, en passant votre exemple 3'rd à un autre objet qui prend et "Animal" type Un appel parle dessus. Si vous le faites, il ne vous aura à vérifier pour chien type, type de chat, et ainsi de suite. Pas sûr si la vérification d'instance est vraiment "Pythonic", à cause de la liaison tardive. Mais alors vous devez mettre en œuvre une certaine façon que le AnimalControl n'essaie pas de jeter les types Cheeseburger dans le camion, parce que Cheeseburgers ne parlez pas.

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"
1
répondu yedevtxt 2012-07-11 14:14:27
Les Classes

en Python sont essentiellement des façons de regrouper un ensemble de fonctions et de données.. Ils sont différents des classes en C++ et autres..

j'ai surtout vu l'héritage utilisé pour surpasser les méthodes de la super-classe. Par exemple, peut-être une utilisation plus Python'ish de l'héritage serait..

from world.animals import Dog

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

bien sûr, les chats ne sont pas un type de chien, mais j'ai ce (tiers) Dog classe qui fonctionne parfaitement, sauf la méthode speak que je veux outrepasser - cela évite de re-implémenter toute la classe, juste pour qu'elle miaule. Encore une fois , alors que Cat n'est pas un type de Dog , mais un chat hérite beaucoup d'attributs..

un bien meilleur exemple (pratique) d'annulation d'une méthode ou d'un attribut est la façon dont vous changez l'agent utilisateur pour urllib. Vous essentiellement sous-classe urllib.FancyURLopener et de changer l'attribut de version ( de la documentation ):

import urllib

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

urllib._urlopener = AppURLopener()

D'autres exceptions sont utilisées pour les Exceptions, lorsque l'héritage est utilisé d'une manière plus "appropriée":

class AnimalError(Exception):
    pass

class AnimalBrokenLegError(AnimalError):
    pass

class AnimalSickError(AnimalError):
    pass

..vous pouvez alors attraper AnimalError pour attraper toutes les exceptions qui héritent de lui, ou un spécifique comme AnimalBrokenLegError

0
répondu dbr 2009-06-24 23:14:51