@ La réponse de Oddthinking n'est pas mal, mais je pense qu'il manque le réel , pratique la raison Python a ABCs dans un monde de canard frappe.
Les méthodes abstraites sont soignées, mais à mon avis, elles ne remplissent pas vraiment les cas d'utilisation qui ne sont pas déjà couverts par le typage canard. La puissance réelle des classes de base abstraites réside dans la façon dont elles vous permettent de personnaliser le comportement de isinstanceetissubclass . ( __subclasshook__est fondamentalement une API plus conviviale en plus de Python __instancecheck__et des__subclasscheck__ hooks.) L'adaptation de constructions intégrées pour travailler sur des types personnalisés fait partie intégrante de la philosophie de Python.
Le code source de Python est exemplaire. Voici comment collections.Containerest défini dans la bibliothèque standard (au moment de la rédaction):
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
Cette définition de __subclasshook__ dit que toute classe avec un __contains__attribut est considérée comme une sous-classe de Container, même si elle ne le sous-classe pas directement. Je peux donc écrire ceci:
class ContainAllTheThings(object):
def __contains__(self, item):
return True
>>> issubclass(ContainAllTheThings, collections.Container)
True
>>> isinstance(ContainAllTheThings(), collections.Container)
True
En d'autres termes, si vous implémentez la bonne interface, vous êtes une sous-classe! Les ABC fournissent un moyen formel de définir des interfaces en Python, tout en restant fidèle à l'esprit de la frappe de canard. En outre, cela fonctionne d'une manière qui respecte le principe ouvert-fermé .
Le modèle objet de Python ressemble superficiellement à celui d'un système OO plus "traditionnel" (par lequel je veux dire Java *) - nous avons des classes yer, des objets yer, des méthodes yer - mais lorsque vous grattez la surface, vous trouverez quelque chose de beaucoup plus riche et plus flexible. De même, la notion de Python de classes de base abstraites peut être reconnaissable par un développeur Java, mais en pratique, elles sont destinées à un objectif très différent.
Je me retrouve parfois à écrire des fonctions polymorphes qui peuvent agir sur un seul élément ou une collection d'éléments, et je trouve isinstance(x, collections.Iterable) être beaucoup plus lisible que hasattr(x, '__iter__')ou un try...exceptbloc équivalent . (Si vous ne connaissiez pas Python, lequel de ces trois rendrait l'intention du code plus claire?)
Cela dit, je trouve que j'ai rarement besoin d'écrire mon propre ABC et je découvre généralement le besoin d'un refactoring. Si je vois une fonction polymorphe faire beaucoup de vérifications d'attributs, ou beaucoup de fonctions faire les mêmes vérifications d'attributs, cette odeur suggère l'existence d'un ABC en attente d'extraction.
* sans entrer dans le débat sur la question de savoir si Java est un système OO "traditionnel" ...
Addendum : Même si une classe de base abstraite peut remplacer le comportement de isinstanceand issubclass, elle n'entre toujours pas dans le MRO de la sous-classe virtuelle. C'est un piège potentiel pour les clients: tous les objets pour lesquels isinstance(x, MyABC) == Trueles méthodes sont définies ne sont pas tous définis 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
Malheureusement, celui-ci de ces pièges «ne faites pas ça» (dont Python a relativement peu!): Évitez de définir ABC avec __subclasshook__des méthodes a et non abstraites. De plus, vous devez rendre votre définition de __subclasshook__cohérente avec l'ensemble des méthodes abstraites définies par votre ABC.
__contains__et une classe qui héritecollections.Container? Dans votre exemple, en Python, il y avait toujours une compréhension partagée de__str__. L'implémentation__str__fait les mêmes promesses que l'héritage de certains ABC puis l'implémentation__str__. Dans les deux cas, vous pouvez rompre le contrat; il n'y a pas de sémantique prouvable comme celles que nous avons en typage statique.