@ 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 isinstance
etissubclass
. ( __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.Container
est 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...except
bloc é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 isinstance
and 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) == True
les 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.