Wie implementiere ich virtuelle Methoden in Python?

85

Ich kenne virtuelle Methoden aus PHP oder Java.

Wie können sie in Python implementiert werden?

Oder muss ich eine leere Methode in einer abstrakten Klasse definieren und überschreiben?

Meloun
quelle

Antworten:

102

Sicher, und Sie müssen nicht einmal eine Methode in der Basisklasse definieren. In Python sind Methoden besser als virtuell - sie sind vollständig dynamisch, da die Eingabe in Python die Eingabe von Enten ist .

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

Catund Dogin Python müssen Sie nicht einmal von einer gemeinsamen Basisklasse ableiten, um dieses Verhalten zuzulassen - Sie erhalten es kostenlos. Einige Programmierer bevorzugen es jedoch, ihre Klassenhierarchien strenger zu definieren, um sie besser zu dokumentieren und eine gewisse Strenge bei der Eingabe aufzuerlegen . Dies ist auch möglich - siehe zum Beispiel das abcStandardmodul .

Eli Bendersky
quelle
30
+1 für ein Beispiel. In welcher Sprache sagen Hunde übrigens "hau"?
JeremyP
4
@JeremyP: hmm, guter Punkt :-) Ich denke in Sprachen, in denen "h" so verstanden wird, dass der Klang wie der erste Buchstabe von "Hippie" oder von "Javier" auf Spanisch ist.
Eli Bendersky
4
@Eli: Sorry, aber ich war ernsthaft an der Antwort auf die Frage interessiert. Auf Englisch sagen sie "woof", nun ja, aber das ist das Wort, das wir analog zu "meow" für Katzen und "moo" für Kühe verwenden. Ist "hau" dann Spanisch?
JeremyP
9
@ JeremyP Ich weiß sicher, dass polnische Hunde das sagen;)
j_kubik
2
@JeremyP Ja, ich bestätige, dass Hunde "Jau" auf Spanisch sagen und wenn sie auf Englisch geschrieben sind, wäre dies "Hau" :) hth
SkyWalker
62

raise NotImplementedError()

Dies ist die empfohlene Ausnahme für "reine virtuelle Methoden" von "abstrakten" Basisklassen, die keine Methode implementieren.

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

Diese Ausnahme leitet sich ab von RuntimeError. In benutzerdefinierten Basisklassen sollten abstrakte Methoden diese Ausnahme auslösen, wenn abgeleitete Klassen zum Überschreiben der Methode erforderlich sind.

Wie andere sagten, handelt es sich meistens um eine Dokumentationskonvention, die nicht erforderlich ist. Auf diese Weise erhalten Sie jedoch eine aussagekräftigere Ausnahme als einen fehlenden Attributfehler.

Z.B:

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

gibt:

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

Verwandte: Ist es möglich, abstrakte Klassen in Python zu erstellen?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
52

Python-Methoden sind immer virtuell.

Ignacio Vazquez-Abrams
quelle
Mit Ausnahme von Dunder-Methoden.
Konstantin
Diese Antwort hilft nicht wirklich beim Ziel, Schnittstellenklassen zu implementieren, was einer der Hauptgründe für die Verwendung virtueller Methoden ist.
Jean-Marc Volle
21

Tatsächlich bietet Python in Version 2.6 so genannte abstrakte Basisklassen, und Sie können virtuelle Methoden wie folgt explizit festlegen:

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

Es funktioniert sehr gut, vorausgesetzt, die Klasse erbt nicht von Klassen, die bereits Metaklassen verwenden.

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

user2795020
quelle
Gibt es ein Python 3-Äquivalent für diese Direktive?
Locke14
9

Python-Methoden sind immer virtuell

wie Ignacio noch sagte Irgendwie kann Klassenvererbung ein besserer Ansatz sein, um das zu implementieren, was Sie wollen.

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

Ergebnisse sollten sein:

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

So etwas wie eine virtuelle Methode in C ++ (Aufruf der Methodenimplementierung einer abgeleiteten Klasse über eine Referenz oder einen Zeiger auf die Basisklasse) ist in Python nicht sinnvoll, da Python keine Typisierung hat. (Ich weiß allerdings nicht, wie virtuelle Methoden in Java und PHP funktionieren.)

Wenn Sie jedoch mit "virtuell" die unterste Implementierung in der Vererbungshierarchie aufrufen, erhalten Sie dies immer in Python, wie mehrere Antworten zeigen.

Na ja, fast immer ...

Wie dplamp hervorhob, verhalten sich nicht alle Methoden in Python so. Dunder Methode nicht. Und ich denke, das ist eine nicht so bekannte Funktion.

Betrachten Sie dieses künstliche Beispiel

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

Jetzt

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

Betrachten Sie jedoch diesen

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

Jetzt

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

Das einzige, was wir geändert haben, war zu machen prop_a() eine Dunder-Methode.

Ein Problem mit dem ersten Verhalten kann sein, dass Sie das Verhalten prop_a()in der abgeleiteten Klasse nicht ändern können, ohne das Verhalten von zu beeinflussen prop_b(). Dieser sehr schöne Vortrag von Raymond Hettinger gibt ein Beispiel für einen Anwendungsfall, bei dem dies unpraktisch ist.

Konstantin
quelle