La risposta di Oddthinking non è sbagliata, ma penso che manchi il vero , pratico motivo per cui Python ha l'ABC in un mondo di dattilografia.
I metodi astratti sono accurati, ma secondo me in realtà non riempiono tutti i casi d'uso non già coperti dalla digitazione dell'anatra. Il vero potere delle classi di base astratte sta nel modo in cui consentono di personalizzare il comportamento di isinstance
eissubclass
. ( __subclasshook__
è fondamentalmente un'API più intuitiva in cima a Python __instancecheck__
e agli__subclasscheck__
hook). L'adattamento dei costrutti integrati per lavorare su tipi personalizzati fa molto parte della filosofia di Python.
Il codice sorgente di Python è esemplare. Ecco come collections.Container
viene definito nella libreria standard (al momento della scrittura):
class Container(metaclass=ABCMeta):
__slots__ = ()
@abstractmethod
def __contains__(self, x):
return False
@classmethod
def __subclasshook__(cls, C):
if cls is Container:
if any("__contains__" in B.__dict__ for B in C.__mro__):
return True
return NotImplemented
Questa definizione di __subclasshook__
dice che qualsiasi classe con un __contains__
attributo è considerata una sottoclasse di Container, anche se non la subclasse direttamente. Quindi posso scrivere questo:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
In altre parole, se si implementa l'interfaccia giusta, si è una sottoclasse! Gli ABC forniscono un modo formale per definire le interfacce in Python, pur rimanendo fedeli allo spirito della battitura delle anatre. Inoltre, questo funziona in modo da onorare il principio aperto-chiuso .
Il modello a oggetti di Python sembra superficialmente simile a quello di un sistema OO più "tradizionale" (con questo intendo Java *) - abbiamo le tue classi, i tuoi oggetti, i tuoi metodi - ma quando gratti la superficie troverai qualcosa di molto più ricco e più flessibile. Allo stesso modo, la nozione di Python di classi base astratte può essere riconoscibile per uno sviluppatore Java, ma in pratica sono intesi per uno scopo molto diverso.
A volte mi ritrovo a scrivere funzioni polimorfiche che possono agire su un singolo oggetto o una raccolta di oggetti e trovo isinstance(x, collections.Iterable)
molto più leggibile di hasattr(x, '__iter__')
un try...except
blocco equivalente . (Se non conoscessi Python, quale di questi tre renderebbe più chiara l'intenzione del codice?)
Detto questo, trovo che raramente ho bisogno di scrivere la mia ABC e in genere scopro la necessità di uno attraverso il refactoring. Se vedo una funzione polimorfica eseguire molti controlli degli attributi o molte funzioni che eseguono gli stessi controlli degli attributi, quell'odore suggerisce l'esistenza di una ABC in attesa di essere estratta.
* senza entrare nel dibattito sul fatto che Java sia un sistema OO "tradizionale" ...
Addendum : anche se una classe base astratta può sovrascrivere il comportamento di isinstance
e issubclass
, tuttavia non entra nell'MRO della sottoclasse virtuale. Questa è una potenziale trappola per i clienti: non tutti gli oggetti per i quali isinstance(x, MyABC) == True
sono stati definiti i metodi MyABC
.
class MyABC(metaclass=abc.ABCMeta):
def abc_method(self):
pass
@classmethod
def __subclasshook__(cls, C):
return True
class C(object):
pass
# typical client code
c = C()
if isinstance(c, MyABC): # will be true
c.abc_method() # raises AttributeError
Purtroppo questa di quelle trappole "semplicemente non farlo" (di cui Python ne ha relativamente poche!): Evita di definire ABC con __subclasshook__
metodi sia a che non astratti. Inoltre, dovresti rendere __subclasshook__
coerente la tua definizione con l'insieme di metodi astratti definiti dalla tua ABC.
__contains__
e una classe che ereditacollections.Container
? Nel tuo esempio, in Python c'era sempre una comprensione condivisa di__str__
. L'implementazione__str__
fa le stesse promesse ereditate da alcuni ABC e quindi implementate__str__
. In entrambi i casi è possibile interrompere il contratto; non ci sono semantiche dimostrabili come quelle che abbiamo nella tipizzazione statica.