¿Cómo implementar métodos virtuales en Python?

88

Conozco métodos virtuales de PHP o Java.

¿Cómo se pueden implementar en Python?

¿O tengo que definir un método vacío en una clase abstracta y anularlo?

Meloun
fuente

Respuestas:

104

Claro, y ni siquiera tiene que definir un método en la clase base. En Python, los métodos son mejores que los virtuales: son completamente dinámicos, ya que escribir en Python es como 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()

Caty Dogen Python ni siquiera tienes que derivar de una clase base común para permitir este comportamiento, lo obtienes gratis. Dicho esto, algunos programadores prefieren definir sus jerarquías de clases de una manera más rígida para documentarlo mejor e imponer algo de rigor en la escritura. Esto también es posible; consulte, por ejemplo, el abcmódulo estándar .

Eli Bendersky
fuente
32
+1 por ejemplo. Por cierto, ¿en qué idioma los perros dicen "hau"?
JeremyP
4
@JeremyP: hmm, buen punto :-) Supongo que en idiomas donde se entiende por "h" que suena como la primera letra de "hippy", o de "Javier" en español.
Eli Bendersky
4
@Eli: Lo siento, pero estaba muy interesado en la respuesta a la pregunta. En inglés dicen "guau", bueno no lo hacen, pero esa es la palabra que usamos análoga a "miau" para gatos y "muu" para vacas. ¿Es "hau" español entonces?
JeremyP
9
@JeremyP sé con certeza que eso es lo que dicen los perros polacos;)
j_kubik
2
@JeremyP sí, confirmo que los perros dicen "Jau" en español y cuando se escribe en inglés sería "Hau" :) hth
SkyWalker
67

raise NotImplementedError()

Esta es la excepción recomendada para generar "métodos virtuales puros" de clases base "abstractas" que no implementan un método.

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

Esta excepción se deriva de RuntimeError. En las clases base definidas por el usuario, los métodos abstractos deben generar esta excepción cuando requieran que las clases derivadas anulen el método.

Como dijeron otros, esto es principalmente una convención de documentación y no es obligatorio, pero de esta manera obtiene una excepción más significativa que un error de atributo faltante.

P.ej:

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

da:

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: ¿Es posible hacer clases abstractas en Python?

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

Los métodos de Python son siempre virtuales.

Ignacio Vázquez-Abrams
fuente
Excepto por métodos más dunder.
Konstantin
Esta respuesta realmente no ayuda en el objetivo de implementar clases de interfaz, que es una de las principales razones para usar métodos virtuales.
Jean-Marc Volle
21

En realidad, en la versión 2.6, Python proporciona algo llamado clases base abstractas y puede establecer métodos virtuales explícitamente como este:

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

Funciona muy bien, siempre que la clase no herede de clases que ya usan metaclases.

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

usuario2795020
fuente
¿Existe un equivalente de Python 3 para esta directiva?
locke14
9

Los métodos de Python son siempre virtuales

como dijo Ignacio todavía De alguna manera, la herencia de clases puede ser un mejor enfoque para implementar lo que desea.

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

Los resultados deben ser:

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

Algo como un método virtual en C ++ (llamar a la implementación del método de una clase derivada a través de una referencia o puntero a la clase base) no tiene sentido en Python, ya que Python no tiene escritura. (Sin embargo, no sé cómo funcionan los métodos virtuales en Java y PHP).

Pero si por "virtual" te refieres a llamar a la implementación más baja en la jerarquía de herencia, entonces eso es lo que siempre obtienes en Python, como señalan varias respuestas.

Bueno, casi siempre ...

Como señaló dplamp, no todos los métodos en Python se comportan así. El método Dunder no lo hace. Y creo que esa es una característica no tan conocida.

Considere este ejemplo 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

Ahora

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

Sin embargo, 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

Ahora

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

Lo único que hemos cambiado fue hacer prop_a()un método más dunder.

Un problema con el primer comportamiento puede ser que no se puede cambiar el comportamiento de prop_a()en la clase derivada sin afectar el comportamiento de prop_b(). Esta bonita charla de Raymond Hettinger ofrece un ejemplo de un caso de uso en el que esto es inconveniente.

Konstantin
fuente