Version courte
Les ABC offrent un niveau de contrat sémantique plus élevé entre les clients et les classes implémentées.
Version longue
Il existe un contrat entre une classe et ses appelants. La classe promet de faire certaines choses et d'avoir certaines propriétés.
Le contrat comporte différents niveaux.
À un niveau très bas, le contrat peut inclure le nom d'une méthode ou son nombre de paramètres.
Dans un langage de type statique, ce contrat serait effectivement appliqué par le compilateur. En Python, vous pouvez utiliser EAFP ou taper introspection pour confirmer que l'objet inconnu respecte ce contrat attendu.
Mais il y a aussi des promesses sémantiques de plus haut niveau dans le contrat.
Par exemple, s'il existe une __str__()
méthode, elle devrait renvoyer une représentation sous forme de chaîne de l'objet. Il pourrait supprimer tout le contenu de l'objet, valider la transaction et cracher une page vierge hors de l'imprimante ... mais il y a une compréhension commune de ce qu'il devrait faire, décrite dans le manuel Python.
C'est un cas particulier, où le contrat sémantique est décrit dans le manuel. Que doit faire la print()
méthode? Doit-il écrire l'objet sur une imprimante ou une ligne sur l'écran, ou autre chose? Cela dépend - vous devez lire les commentaires pour comprendre l'intégralité du contrat ici. Un morceau de code client qui vérifie simplement que la print()
méthode existe a confirmé une partie du contrat - qu'un appel de méthode peut être effectué, mais pas qu'il existe un accord sur la sémantique de niveau supérieur de l'appel.
La définition d'une classe de base abstraite (ABC) est un moyen de produire un contrat entre les implémenteurs de classe et les appelants. Ce n'est pas seulement une liste de noms de méthodes, mais une compréhension partagée de ce que ces méthodes doivent faire. Si vous héritez de cet ABC, vous vous engagez à suivre toutes les règles décrites dans les commentaires, y compris la sémantique de la print()
méthode.
Le typage canard de Python présente de nombreux avantages en termes de flexibilité par rapport au typage statique, mais il ne résout pas tous les problèmes. Les ABC offrent une solution intermédiaire entre la forme libre de Python et l'esclavage et la discipline d'un langage de type statique.
__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.collections.Container
est un cas dégénéré, qui n'inclut\_\_contains\_\_
, et ne signifie que la convention prédéfinie. L'utilisation d'un ABC n'ajoute pas beaucoup de valeur en soi, je suis d'accord. Je soupçonne qu'il a été ajouté pour permettre (par exemple)Set
d'en hériter. Au moment où vous y arrivezSet
, l'appartenance soudaine à l'ABC a une sémantique considérable. Un article ne peut pas appartenir deux fois à la collection. Ce n'est PAS détectable par l'existence de méthodes.Set
c'est un meilleur exemple queprint()
. J'essayais de trouver un nom de méthode dont la signification était ambiguë, et ne pouvait pas être bloqué par le seul nom, donc vous ne pouviez pas être sûr qu'il ferait la bonne chose juste par son nom et le manuel Python.Set
comme exemple au lieu deprint
?Set
a beaucoup de sens, @Oddthinking.@ 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):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: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 quehasattr(x, '__iter__')
ou untry...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
andissubclass
, 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 lesquelsisinstance(x, MyABC) == True
les méthodes sont définies ne sont pas tous définisMyABC
.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.la source
isinstance(x, collections.Iterable)
c'est plus clair pour moi, et je connais Python.C
sous - classe supprimée (ou foiré au-delà de toute réparation) dont elle aabc_method()
héritéMyABC
. La principale différence est que c'est la superclasse qui fout le contrat de succession, pas la sous-classe.Container.register(ContainAllTheThings)
pour que l'exemple donné fonctionne?__subclasshook__
est "toute classe qui satisfait à ce prédicat est considérée comme une sous-classe aux fins deisinstance
etissubclass
vérifie, qu'elle soit enregistrée auprès de l'ABC et qu'elle soit une sous-classe directe ". Comme je l'ai dit dans la réponse, si vous implémentez la bonne interface, vous êtes une sous-classe!Une caractéristique pratique des ABC est que si vous n'implémentez pas toutes les méthodes (et propriétés) nécessaires, vous obtenez une erreur lors de l'instanciation, plutôt qu'un
AttributeError
, potentiellement beaucoup plus tard, lorsque vous essayez réellement d'utiliser la méthode manquante.Exemple de https://dbader.org/blog/abstract-base-classes-in-python
Edit: pour inclure la syntaxe python3, merci @PandasRocks
la source
Cela rendra plus facile de déterminer si un objet prend en charge un protocole donné sans avoir à vérifier la présence de toutes les méthodes dans le protocole ou sans déclencher une exception profondément en territoire "ennemi" en raison d'un non-support.
la source
La méthode abstraite s'assure que la méthode que vous appelez dans la classe parent doit apparaître dans la classe enfant. Vous trouverez ci-dessous la manière noraml d'appeler et d'utiliser le résumé. Le programme écrit en python3
Manière normale d'appeler
Avec la méthode abstraite
Puisque methodtwo n'est pas appelé dans la classe enfant, nous avons eu une erreur. La bonne mise en œuvre est ci-dessous
la source