Как реализовать виртуальные методы в 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: Извините, но я серьезно заинтересовался ответом на вопрос. По-английски они говорят «гав», ну, они этого не делают, но это слово мы используем аналогично «мяу» для кошек и «мычание» для коров. Значит, "хау" испанское?
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?

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
53

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

user2795020
источник
Есть ли для этой директивы эквивалент 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
Jinzo
источник
4

Что-то вроде виртуального метода в C ++ (реализация метода вызова производного класса через ссылку или указатель на базовый класс) не имеет смысла в 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(). В этом прекрасном выступлении Раймонда Хеттингера приводится пример использования, в котором это неудобно.

Константин
источник