Come implementare metodi virtuali in Python?


88

Conosco metodi virtuali da PHP o Java.

Come possono essere implementati in Python?

O devo definire un metodo vuoto in una classe astratta e sovrascriverlo?

Risposte:


104

Certo, e non devi nemmeno definire un metodo nella classe base. In Python i metodi sono migliori di quelli virtuali: sono completamente dinamici, poiché la digitazione in Python è la digitazione a papera .

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 Dogin Python non è nemmeno necessario derivare da una classe base comune per consentire questo comportamento: lo ottieni gratuitamente. Detto questo, alcuni programmatori preferiscono definire le loro gerarchie di classi in un modo più rigido per documentarlo meglio e imporre un certo rigore di battitura. Anche questo è possibile - vedi ad esempio il abcmodulo standard .


35
+1 per un esempio. A proposito, in che lingua i cani dicono "hau"?
JeremyP

5
@ JeremyP: hmm, buon punto :-) Immagino che nelle lingue in cui "h" si intenda come la prima lettera di "hippy", o di "Javier" in spagnolo.
Eli Bendersky

5
@Eli: Scusa, ma ero seriamente interessato alla risposta alla domanda. In inglese si dice "woof", beh non lo fanno, ma questa è la parola che usiamo analoga a "miaow" per i gatti e "moo" per le mucche. "Hau" è spagnolo allora?
JeremyP

9
@ JeremyP So per certo che è quello che dicono i cani polacchi;)
j_kubik

2
@ JeremyP sì confermo che i cani dicono "Jau" in spagnolo e quando scritto in inglese sarebbe "Hau" :) hth
SkyWalker

71

raise NotImplementedError()

Questa è l'eccezione consigliata da sollevare sui "metodi virtuali puri" di classi base "astratte" che non implementano un metodo.

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

Questa eccezione è derivata da RuntimeError. Nelle classi base definite dall'utente, i metodi astratti dovrebbero sollevare questa eccezione quando richiedono che le classi derivate sovrascrivano il metodo.

Come altri hanno detto, questa è principalmente una convenzione di documentazione e non è richiesta, ma in questo modo si ottiene un'eccezione più significativa di un errore di attributo mancante.

Per esempio:

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

Correlati: è possibile creare classi astratte in Python?


55

I metodi Python sono sempre virtuali.


Fatta eccezione per i metodi stupidi.
Konstantin

1
Questa risposta non aiuta nell'obiettivo di implementare classi di interfaccia, che è una delle ragioni principali per l'utilizzo di metodi virtuali.
Jean-Marc Volle

21

In realtà, nella versione 2.6 python fornisce qualcosa chiamato classi base astratte e puoi impostare esplicitamente metodi virtuali come questo:

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

Funziona molto bene, a condizione che la classe non erediti da classi che già utilizzano metaclassi.

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


Esiste un equivalente di Python 3 per questa direttiva?
locke14

9

I metodi Python sono sempre virtuali

come ha detto Ignacio, in qualche modo l'ereditarietà di classe può essere un approccio migliore per implementare ciò che si desidera.

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 risultati dovrebbero essere:

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

4

Qualcosa come un metodo virtuale in C ++ (chiamare l'implementazione del metodo di una classe derivata tramite un riferimento o un puntatore alla classe base) non ha senso in Python, poiché Python non ha la digitazione. (Non so come funzionano i metodi virtuali in Java e PHP.)

Ma se per "virtuale" intendi chiamare l'implementazione più in basso nella gerarchia di ereditarietà, allora questo è ciò che ottieni sempre in Python, come sottolineano diverse risposte.

Beh, quasi sempre ...

Come ha sottolineato dplamp, non tutti i metodi in Python si comportano in questo modo. Il metodo Dunder no. E penso che sia una caratteristica non così conosciuta.

Considera questo esempio artificiale

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

Adesso

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

Tuttavia, considera questo

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

Adesso

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

L'unica cosa che abbiamo cambiato è stato creare prop_a()un metodo stupido.

Un problema con il primo comportamento può essere che non è possibile modificare il comportamento di prop_a()nella classe derivata senza influire sul comportamento di prop_b(). Questo bellissimo discorso di Raymond Hettinger fornisce un esempio di un caso d'uso in cui ciò è scomodo.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.