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:
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()
Cat
e Dog
in 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 abc
modulo standard .
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
I metodi Python sono sempre virtuali.
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.
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
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.