Як реалізувати віртуальні методи в Python?


88

Я знаю віртуальні методи з PHP або Java.

Як їх можна реалізувати в Python?

Або я повинен визначити порожній метод в абстрактному класі і замінити його?

Відповіді:


104

Звичайно, і вам навіть не потрібно визначати метод у базовому класі. У 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стандартний модуль .


32
+1 для прикладу. Якою мовою собаки кажуть "хау", до речі?
JeremyP

4
@JeremyP: хм, хороший момент :-) Я думаю, на мовах, де "h" розуміється як видавання звуку, як перша буква "хіпі", або "Хав'єр" іспанською.
Елі Бендерський,

4
@Eli: Вибачте, але мене серйозно зацікавила відповідь на запитання. По-англійськи вони кажуть "woof", ну ні, але це слово, яке ми використовуємо аналогічно "мяу" для котів і "му" для корів. Тоді "хау" іспанська?
JeremyP

9
@JeremyP Я точно знаю, що так говорять польські собаки;)
j_kubik

2
@JeremyP так, я підтверджую, що собаки кажуть "Jau" по-іспанськи, а якщо писати англійською, це буде "Hau" :) hth
SkyWalker,

67

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?



21

Насправді, у версії 2.6 python пропонує щось, що називається абстрактними базовими класами, і ви можете явно встановити такі віртуальні методи:

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

Це працює дуже добре, за умови, що клас не успадковує від класів, які вже використовують метакласи.

джерело: http://docs.python.org/2/library/abc.html


Чи існує еквівалент python 3 для цієї директиви?
locke14

9

Методи 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

4

Щось на зразок віртуального методу в С ++ (виклик реалізації методу похідного класу через посилання або вказівник на базовий клас) не має сенсу в 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(). Ця дуже приємна розмова Реймонда Хеттінгера дає приклад для випадку використання, коли це незручно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.