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ą?
Odpowiedzi:
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()
Cat
aw Dog
Pythonie 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 abc
standardowy moduł .
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
Metody Pythona są zawsze wirtualne.
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.
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
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.