Como implementar métodos virtuais em Python?

88

Eu conheço métodos virtuais de PHP ou Java.

Como eles podem ser implementados em Python?

Ou devo definir um método vazio em uma classe abstrata e substituí-lo?

Meloun
fonte

Respostas:

104

Claro, e você nem precisa definir um método na classe base. Em Python, os métodos são melhores do que virtuais - eles são completamente dinâmicos, pois a digitação em Python é uma digitação de pato .

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

Cate Dogem Python nem mesmo precisa derivar de uma classe base comum para permitir esse comportamento - você o obtém de graça. Dito isso, alguns programadores preferem definir suas hierarquias de classes de uma maneira mais rígida para documentá-las melhor e impor alguma rigidez de digitação. Isso também é possível - consulte, por exemplo, o abcmódulo padrão .

Eli Bendersky
fonte
32
1 para um exemplo. A propósito, em que idioma os cães dizem "hau"?
JeremyP
4
@JeremyP: hmm, bom ponto :-) Acho que em idiomas onde "h" é entendido como fazendo o som da primeira letra de "hippy" ou de "Javier" em espanhol.
Eli Bendersky
4
@Eli: Desculpe, mas eu estava seriamente interessado na resposta à pergunta. Em inglês eles dizem "woof", bem, eles não dizem, mas essa é a palavra que usamos, análoga a "miau" para gatos e "moo" para vacas. Então "hau" é espanhol?
JeremyP
9
@JeremyP Eu sei com certeza que é o que os cães poloneses dizem;)
j_kubik
2
@JeremyP sim, eu confirmo que os cães dizem "Jau" em espanhol e quando escrito em inglês seria "Hau" :) hth
SkyWalker
67

raise NotImplementedError()

Esta é a exceção recomendada para gerar "métodos virtuais puros" de classes base "abstratas" que não implementam um método.

https://docs.python.org/3.5/library/exceptions.html#NotImplementedError diz:

Esta exceção é derivada de RuntimeError. Em classes base definidas pelo usuário, os métodos abstratos devem gerar essa exceção quando requerem classes derivadas para substituir o método.

Como outros disseram, esta é principalmente uma convenção de documentação e não é necessária, mas desta forma você obtém uma exceção mais significativa do que um erro de atributo ausente.

Por exemplo:

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

dá:

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

Relacionado: É possível fazer classes abstratas em Python?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte
53

Os métodos Python são sempre virtuais.

Ignacio Vazquez-Abrams
fonte
Exceto para métodos Dunder.
Konstantin
Essa resposta não está ajudando muito no objetivo de implementação de classes de interface, que é uma das principais razões para o uso de métodos virtuais.
Jean-Marc Volle
21

Na verdade, na versão 2.6, o python fornece algo chamado classes básicas abstratas e você pode definir métodos virtuais explicitamente como este:

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

Funciona muito bem, desde que a classe não herde de classes que já usam metaclasses.

fonte: http://docs.python.org/2/library/abc.html

user2795020
fonte
Existe um equivalente de python 3 para esta diretiva?
locke14
9

Os métodos Python são sempre virtuais

como Ignacio disse ainda De alguma forma, a herança de classes pode ser uma abordagem melhor para implementar o que você deseja.

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

Os resultados devem ser:

I am an unknown animal
I am a dog named Rover
Rover has 4 legs
Jinzo
fonte
4

Algo como um método virtual em C ++ (chamar a implementação de método de uma classe derivada por meio de uma referência ou ponteiro para a classe base) não faz sentido em Python, pois Python não tem digitação. (Não sei como os métodos virtuais funcionam em Java e PHP.)

Mas se por "virtual" você quer dizer chamar a implementação mais inferior na hierarquia de herança, então é isso que você sempre obtém em Python, como várias respostas apontam.

Bem, quase sempre ...

Como dplamp apontou, nem todos os métodos em Python se comportam assim. O método Dunder não. E acho que esse é um recurso não muito conhecido.

Considere este exemplo artificial

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

Agora

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

No entanto, considere este

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

Agora

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

A única coisa que prop_a()mudamos foi fazer um método dunder.

Um problema com o primeiro comportamento pode ser que você não pode alterar o comportamento de prop_a()na classe derivada sem afetar o comportamento de prop_b(). Esta palestra muito agradável de Raymond Hettinger dá um exemplo de um caso de uso em que isso é inconveniente.

Konstantin
fonte