Comment implémenter des méthodes virtuelles en Python?

88

Je connais les méthodes virtuelles de PHP ou Java.

Comment peuvent-ils être implémentés en Python?

Ou dois-je définir une méthode vide dans une classe abstraite et la remplacer?

Meloun
la source

Réponses:

104

Bien sûr, et vous n'avez même pas besoin de définir une méthode dans la classe de base. En Python, les méthodes sont meilleures que virtuelles - elles sont complètement dynamiques, car le typage en Python est un typage canard .

class Dog:
  def say(self):
    print "hau"

class Cat:
  def say(self):
    print "meow"

pet = Dog()
pet.say() # prints "hau"
another_pet = Cat()
another_pet.say() # prints "meow"

my_pets = [pet, another_pet]
for a_pet in my_pets:
  a_pet.say()

Catet Dogen Python, vous n'avez même pas besoin de dériver d'une classe de base commune pour autoriser ce comportement - vous l'obtenez gratuitement. Cela dit, certains programmeurs préfèrent définir leurs hiérarchies de classes de manière plus rigide pour mieux les documenter et imposer une certaine rigueur de frappe. Ceci est également possible - voir par exemple le abcmodule standard .

Eli Bendersky
la source
32
+1 pour un exemple. Dans quelle langue les chiens disent-ils "hau" au fait?
JeremyP
4
@JeremyP: hmm, bon point :-) Je suppose que dans les langues où "h" est compris comme faisant le son comme la première lettre de "hippy", ou de "Javier" en espagnol.
Eli Bendersky le
4
@Eli: Désolé, mais j'étais sérieusement intéressé par la réponse à la question. En anglais, ils disent "woof", eh bien non, mais c'est le mot que nous utilisons comme "miaou" pour les chats et "moo" pour les vaches. Est-ce que "hau" est espagnol alors?
JeremyP
9
@JeremyP Je sais que c'est ce que disent les chiens polonais;)
j_kubik
2
@JeremyP oui je confirme que les chiens disent "Jau" en espagnol et quand écrit en anglais serait "Hau" :) hth
SkyWalker
67

raise NotImplementedError()

C'est l'exception recommandée à lever sur les «méthodes virtuelles pures» des classes de base «abstraites» qui n'implémentent pas de méthode.

https://docs.python.org/3.5/library/exceptions.html#NotImplementedError dit:

Cette exception est dérivée de RuntimeError. Dans les classes de base définies par l'utilisateur, les méthodes abstraites doivent lever cette exception lorsqu'elles ont besoin de classes dérivées pour remplacer la méthode.

Comme d'autres l'ont dit, il s'agit principalement d'une convention de documentation et n'est pas obligatoire, mais de cette façon, vous obtenez une exception plus significative qu'une erreur d'attribut manquante.

Par exemple:

class Base(object):
    def virtualMethod(self):
        raise NotImplementedError()
    def usesVirtualMethod(self):
        return self.virtualMethod() + 1

class Derived(Base):
    def virtualMethod(self):
        return 1

print Derived().usesVirtualMethod()
Base().usesVirtualMethod()

donne:

2
Traceback (most recent call last):
  File "./a.py", line 13, in <module>
    Base().usesVirtualMethod()
  File "./a.py", line 6, in usesVirtualMethod
    return self.virtualMethod() + 1
  File "./a.py", line 4, in virtualMethod
    raise NotImplementedError()
NotImplementedError

Connexes: Est-il possible de créer des classes abstraites en Python?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
la source
53

Les méthodes Python sont toujours virtuelles.

Ignacio Vazquez-Abrams
la source
Sauf pour les méthodes dunder.
Konstantin
Cette réponse n'aide pas vraiment dans l'objectif d'implémentation des classes d'interface qui est l'une des principales raisons d'utiliser des méthodes virtuelles.
Jean-Marc Volle
21

En fait, dans la version 2.6, python fournit quelque chose appelé classes de base abstraites et vous pouvez définir explicitement des méthodes virtuelles comme celle-ci:

from abc import ABCMeta
from abc import abstractmethod
...
class C:
    __metaclass__ = ABCMeta
    @abstractmethod
    def my_abstract_method(self, ...):

Cela fonctionne très bien, à condition que la classe n'hérite pas des classes qui utilisent déjà des métaclasses.

source: http://docs.python.org/2/library/abc.html

user2795020
la source
Existe-t-il un équivalent python 3 pour cette directive?
locke14
9

Les méthodes Python sont toujours virtuelles

comme Ignacio l'a encore dit, l'héritage de classe peut être une meilleure approche pour implémenter ce que vous voulez.

class Animal:
    def __init__(self,name,legs):
        self.name = name
        self.legs = legs

    def getLegs(self):
        return "{0} has {1} legs".format(self.name, self.legs)

    def says(self):
        return "I am an unknown animal"

class Dog(Animal): # <Dog inherits from Animal here (all methods as well)

    def says(self): # <Called instead of Animal says method
        return "I am a dog named {0}".format(self.name)

    def somethingOnlyADogCanDo(self):
        return "be loyal"

formless = Animal("Animal", 0)
rover = Dog("Rover", 4) #<calls initialization method from animal

print(formless.says()) # <calls animal say method

print(rover.says()) #<calls Dog says method
print(rover.getLegs()) #<calls getLegs method from animal class

Les résultats doivent être:

I am an unknown animal
I am a dog named Rover
Rover has 4 legs
Jinzo
la source
4

Quelque chose comme une méthode virtuelle en C ++ (appelant l'implémentation de méthode d'une classe dérivée via une référence ou un pointeur vers la classe de base) n'a pas de sens en Python, car Python n'a pas de typage. (Je ne sais pas comment les méthodes virtuelles fonctionnent en Java et PHP.)

Mais si par «virtuel» vous entendez appeler l'implémentation la plus basse dans la hiérarchie d'héritage, alors c'est ce que vous obtenez toujours en Python, comme le soulignent plusieurs réponses.

Eh bien, presque toujours ...

Comme dplamp l'a souligné, toutes les méthodes de Python ne se comportent pas comme ça. La méthode Dunder ne le fait pas. Et je pense que ce n'est pas une fonctionnalité très connue.

Prenons cet exemple artificiel

class A:
    def prop_a(self):
        return 1
    def prop_b(self):
        return 10 * self.prop_a()

class B(A):
    def prop_a(self):
        return 2

Maintenant

>>> B().prop_b()
20
>>> A().prob_b()
10

Cependant, considérez celui-ci

class A:
    def __prop_a(self):
        return 1
    def prop_b(self):
        return 10 * self.__prop_a()

class B(A):
    def __prop_a(self):
        return 2

Maintenant

>>> B().prop_b()
10
>>> A().prob_b()
10

La seule chose que nous avons prop_a()modifiée a été de créer une méthode dunder.

Un problème avec le premier comportement peut être que vous ne pouvez pas modifier le comportement de prop_a()dans la classe dérivée sans affecter le comportement de prop_b(). Cette très belle présentation de Raymond Hettinger donne un exemple de cas d'utilisation où cela n'est pas pratique.

Konstantin
la source