Jak zaimplementować metody wirtualne w Pythonie?

88

Znam metody wirtualne z PHP lub Java.

Jak można je zaimplementować w Pythonie?

A może muszę zdefiniować pustą metodę w klasie abstrakcyjnej i zastąpić ją?

Meloun
źródło

Odpowiedzi:

104

Jasne, nie musisz nawet definiować metody w klasie bazowej. W Pythonie metody są lepsze niż wirtualne - są całkowicie dynamiczne, ponieważ pisanie w Pythonie jest pisane kaczką .

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

Cataw DogPythonie nie musisz nawet wyprowadzać ze wspólnej klasy bazowej, aby zezwolić na takie zachowanie - zyskujesz je za darmo. To powiedziawszy, niektórzy programiści wolą definiować swoje hierarchie klas w bardziej sztywny sposób, aby lepiej je udokumentować i narzucić pewną ścisłość pisania. Jest to również możliwe - zobacz na przykład abcstandardowy moduł .

Eli Bendersky
źródło
32
Przykład +1. Nawiasem mówiąc, w jakim języku psy mówią „hau”?
JeremyP
4
@JeremyP: hmm, słuszna uwaga :-) Wydaje mi się, że w językach, w których „h” jest rozumiane jako nadawanie dźwięku jak pierwsza litera „hipis” lub „Javier” w języku hiszpańskim.
Eli Bendersky,
4
@Eli: Przepraszam, ale poważnie zainteresowała mnie odpowiedź na pytanie. Po angielsku mówią „hau”, cóż, nie, ale to jest słowo, którego używamy analogicznie do „miau” dla kotów i „muczenie” dla krów. Czy zatem „hau” jest hiszpańskie?
JeremyP
9
@JeremyP Wiem na pewno, że tak mówią polskie psy;)
j_kubik
2
@JeremyP tak, potwierdzam, że psy mówią „Jau” po hiszpańsku, a po angielsku będzie
brzmiało
67

raise NotImplementedError()

Jest to zalecany wyjątek dotyczący „czystych metod wirtualnych” „abstrakcyjnych” klas podstawowych, które nie implementują żadnej metody.

https://docs.python.org/3.5/library/exceptions.html#NotImplementedError mówi:

Ten wyjątek pochodzi od RuntimeError. W klasach bazowych zdefiniowanych przez użytkownika metody abstrakcyjne powinny zgłaszać ten wyjątek, gdy wymagają klas pochodnych do przesłonięcia metody.

Jak powiedzieli inni, jest to głównie konwencja dokumentacji i nie jest wymagana, ale w ten sposób otrzymujesz bardziej znaczący wyjątek niż błąd brakującego atrybutu.

Na przykład:

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

daje:

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

Powiązane: Czy można tworzyć klasy abstrakcyjne w Pythonie?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
53

Metody Pythona są zawsze wirtualne.

Ignacio Vazquez-Abrams
źródło
Z wyjątkiem głupich metod.
Konstantin,
Ta odpowiedź nie pomaga w realizacji celu implementacji klas interfejsów, co jest jednym z głównych powodów używania metod wirtualnych.
Jean-Marc Volle
21

W rzeczywistości w wersji 2.6 python udostępnia coś, co nazywa się abstrakcyjnymi klasami bazowymi i możesz jawnie ustawić metody wirtualne, takie jak ta:

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

Działa bardzo dobrze, pod warunkiem, że klasa nie dziedziczy po klasach, które już używają metaklas.

źródło: http://docs.python.org/2/library/abc.html

user2795020
źródło
Czy istnieje odpowiednik tej dyrektywy w Pythonie 3?
locke14
9

Metody Pythona są zawsze wirtualne

jak Ignacio powiedział jeszcze, że dziedziczenie klas może być lepszym podejściem do implementacji tego, co chcesz.

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

Wyniki powinny być:

I am an unknown animal
I am a dog named Rover
Rover has 4 legs
Jinzo
źródło
4

Coś w rodzaju metody wirtualnej w C ++ (wywołanie implementacji metody klasy pochodnej przez odniesienie lub wskaźnik do klasy bazowej) nie ma sensu w Pythonie, ponieważ w Pythonie nie ma pisania. (Nie wiem jednak, jak działają metody wirtualne w Javie i PHP).

Ale jeśli przez „wirtualny” masz na myśli wywołanie najniższej implementacji w hierarchii dziedziczenia, to jest to to, co zawsze otrzymujesz w Pythonie, jak wskazuje kilka odpowiedzi.

Cóż, prawie zawsze ...

Jak zauważył dplamp, nie wszystkie metody w Pythonie zachowują się w ten sposób. Metoda Dunder nie. Myślę, że to niezbyt znana funkcja.

Rozważmy ten sztuczny przykład

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

Teraz

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

Jednak rozważ to

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

Teraz

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

Jedyną rzeczą, którą zmieniliśmy, było zrobienie prop_a()głupiej metody.

Problem z pierwszym zachowaniem może polegać na tym, że nie można zmienić zachowania prop_a()w klasie pochodnej bez wpływu na zachowanie prop_b(). Ta bardzo miła przemowa Raymonda Hettingera stanowi przykład zastosowania, w którym jest to niewygodne.

Konstantin
źródło