Я знаю віртуальні методи з PHP або Java.
Як їх можна реалізувати в Python?
Або я повинен визначити порожній метод в абстрактному класі і замінити його?
Відповіді:
Звичайно, і вам навіть не потрібно визначати метод у базовому класі. У Python методи кращі, ніж віртуальні - вони абсолютно динамічні, оскільки введення в Python - це качине введення .
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
а Dog
в Python навіть не потрібно походити із загального базового класу, щоб дозволити таку поведінку - ви отримуєте це безкоштовно. Тим не менш, деякі програмісти вважають за краще визначати свої ієрархії класів більш жорстким способом, щоб краще це документувати та накладати певну строгість набору тексту. Це також можливо - див., Наприклад, abc
стандартний модуль .
raise NotImplementedError()
Це рекомендований виняток для "чисто віртуальних методів" "абстрактних" базових класів, які не реалізують метод.
https://docs.python.org/3.5/library/exceptions.html#NotImplementedError каже:
Цей виняток походить від
RuntimeError
. У визначених користувачем базових класах абстрактні методи повинні викликати цей виняток, коли їм потрібні похідні класи, щоб замінити метод.
Як казали інші, це в основному домовленість щодо документації і не є обов’язковою, але таким чином ви отримуєте більш значущий виняток, ніж відсутність помилки атрибута.
Наприклад:
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()
дає:
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
Пов’язане: Чи можна робити абстрактні класи на Python?
Методи Python завжди віртуальні.
Насправді, у версії 2.6 python пропонує щось, що називається абстрактними базовими класами, і ви можете явно встановити такі віртуальні методи:
from abc import ABCMeta
from abc import abstractmethod
...
class C:
__metaclass__ = ABCMeta
@abstractmethod
def my_abstract_method(self, ...):
Це працює дуже добре, за умови, що клас не успадковує від класів, які вже використовують метакласи.
Методи Python завжди віртуальні
як сказав Ігнасіо, якимось чином спадкування класів може бути кращим підходом для реалізації того, що ви хочете.
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
Результати повинні бути:
I am an unknown animal
I am a dog named Rover
Rover has 4 legs
Щось на зразок віртуального методу в С ++ (виклик реалізації методу похідного класу через посилання або вказівник на базовий клас) не має сенсу в Python, оскільки в Python немає набору тексту. (Хоча я не знаю, як працюють віртуальні методи в Java та PHP.)
Але якщо під "віртуальним" ви маєте на увазі найнижчу реалізацію в ієрархії успадкування, то це те, що ви завжди отримуєте в Python, на що вказує кілька відповідей.
Ну, майже завжди ...
Як зазначив dplamp, не всі методи в Python поводяться так. Метод Дандера - ні. І я думаю, що це не дуже відома особливість.
Розглянемо цей штучний приклад
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
Зараз
>>> B().prop_b()
20
>>> A().prob_b()
10
Однак розгляньте це
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
Зараз
>>> B().prop_b()
10
>>> A().prob_b()
10
Єдине, що ми змінили, - prop_a()
це створення методу дундера.
Проблема з першою поведінкою може полягати в тому, що ви не можете змінити поведінку prop_a()
похідного класу, не впливаючи на поведінку prop_b()
. Ця дуже приємна розмова Реймонда Хеттінгера дає приклад для випадку використання, коли це незручно.