Pourquoi utiliser Résumé des classes de base en Python?
-
01-10-2019 - |
Question
Parce que je suis habitué aux anciennes façons de taper de canard en Python, je ne comprends pas la nécessité d'ABC (classes abstraites de base). aide est bon sur la façon de les utiliser.
J'ai essayé de lire la raison d'être dans le PEP , mais il a au dessus de ma tête. Si je cherchais un conteneur de séquence mutable, je vérifier __setitem__
, ou plus probablement essayer de l'utiliser ( EAFP ). Je ne suis pas venu à travers une utilisation de la vie réelle pour les numéros module, qui ne ABCs d'utilisation, mais qui est le plus proche, je dois comprendre.
Quelqu'un peut-il me expliquer la raison d'être, s'il vous plaît?
La solution
Version courte
ABCs offrent un niveau supérieur de contrat sémantique entre les clients et les classes mises en œuvre.
Version longue
Il y a un contrat entre une classe et ses appelants. Les promesses de la classe à faire certaines choses et ont certaines propriétés.
Il existe différents niveaux au contrat.
A un niveau très bas, le contrat pourrait inclure le nom d'une méthode ou son nombre de paramètres.
Dans un langage statiquement typé, ce contrat serait effectivement appliqué par le compilateur. En Python, vous pouvez utiliser EAFP ou l'introspection pour confirmer que l'objet inconnu répond à ce contrat prévu.
Mais il y a aussi de plus haut niveau, les promesses sémantiques dans le contrat.
Par exemple, s'il existe une méthode de __str__()
, il est prévu de retourner une représentation de chaîne de l'objet. Il peut supprimer tout le contenu de l'objet, valider la transaction et cracher une page blanche de l'imprimante ... mais il y a une compréhension commune de ce qu'il doit faire, décrit dans le manuel Python.
C'est un cas particulier, lorsque le contrat sémantique est décrit dans le manuel. Qu'est-ce que la méthode print()
doit faire? Faut-il écrire l'objet d'une imprimante ou d'une ligne à l'écran, ou autre chose? Cela dépend - vous devez lire les commentaires pour comprendre le contrat complet ici. Un morceau de code client qui simplement vérifie que la méthode print()
existe a confirmé une partie du contrat -. Qu'un appel de méthode peut être, mais pas qu'il y ait un accord sur la sémantique de niveau supérieur de l'appel
Définition d'une classe abstraite de base (ABC) est un moyen de produire un contrat entre les implémenteurs de classe et les appelants. Il est non seulement une liste de noms de méthode, mais une compréhension partagée de ce que ces méthodes devraient faire. Si vous héritez de cette ABC, vous promettez de suivre toutes les règles décrites dans les commentaires, y compris la sémantique de la méthode print()
.
canard de frappe Python présente de nombreux avantages en termes de flexibilité sur-typage statique, mais il ne résout pas tous les problèmes. ABC offrent une solution intermédiaire entre la forme libre de Python et de la servitude et la discipline d'un langage statiquement typé.
Autres conseils
@ La réponse de Oddthinking n'est pas mal, mais je pense qu'il manque le real , pratique raison Python a ABCs dans un monde de canard frappe.
Les méthodes abstraites sont propres, mais à mon avis, ils ne sont pas vraiment remplir les cas d'utilisation ne sont pas déjà couverts par la saisie de canard. puissance réelle de classes de base abstraites réside dans la façon dont ils vous permettent de personnaliser le comportement des isinstance
et issubclass
. (__subclasshook__
est essentiellement une API conviviale au-dessus de son Python __instancecheck__
et __subclasscheck__
crochets.) l'adaptation intégrée des constructions à travailler sur des types personnalisés est tout à fait partie 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 la classe avec un attribut __contains__
est considéré comme une sous-classe de conteneur, même si elle ne la 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 l'interface droite, vous êtes une sous-classe! ABCs fournissent une manière formelle pour définir les interfaces en Python, tout en restant fidèle à l'esprit de canard frappe. D'ailleurs, cela fonctionne d'une manière qui honore ouvert-fermé Principe .
modèle objet de Python regards superficiellement similaire à celle d'un système plus OO « traditionnel » (je veux dire par Java *) - nous avons yer classes, yer objets, yer méthodes - mais quand vous grattez la surface, vous trouverez quelque chose beaucoup plus riche et plus souple. De même, la notion de Python de classes de base abstraites peut être reconnaissable à un développeur Java, mais dans la pratique, ils sont destinés à un usage très différent.
Je trouve parfois moi-même écrire des fonctions polymorphes qui peuvent agir sur un seul élément ou une collection d'articles, et je trouve isinstance(x, collections.Iterable)
beaucoup plus lisible que hasattr(x, '__iter__')
ou un bloc équivalent try...except
. (Si vous ne saviez pas Python, qui de ces trois rendrait l'intention de la plus claire de code?)
Cela dit, je trouve que j'ai rarement besoin d'écrire mon propre ABC et je découvre généralement la nécessité d'un à refactoring. Si je vois une fonction polymorphique faire beaucoup de contrôles d'attributs, ou beaucoup de fonctions qui font les mêmes contrôles d'attributs, cette odeur suggère l'existence d'un ABC en attente d'être extrait.
* sans entrer dans le débat 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
et issubclass
, il ne reste pas entrer dans le MRO de la sous-classe virtuelle. Ceci est un piège potentiel pour les clients. Pas tous les objets pour lesquels isinstance(x, MyABC) == True
a les méthodes définies sur 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 ceux « juste ne pas le faire » pièges (dont Python a relativement peu!): Éviter de définir ABCs à la fois un __subclasshook__
et des méthodes non abstraites. De plus, vous devez faire votre définition de __subclasshook__
compatible avec l'ensemble des méthodes abstraites vos définit de ABC.
Une caractéristique pratique de VARA est que si vous ne mettez pas en œuvre toutes les méthodes nécessaires (et propriétés) vous obtenez une erreur sur instanciation, plutôt qu'une AttributeError
, potentiellement beaucoup plus tard, lorsque vous essayez réellement d'utiliser la méthode manquante.
from abc import ABCMeta, abstractmethod
# python2
class Base(object):
__metaclass__ = ABCMeta
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
# python3
class Base(object, metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
@abstractmethod
def bar(self):
pass
class Concrete(Base):
def foo(self):
pass
# We forget to declare `bar`
c = Concrete()
# TypeError: "Can't instantiate abstract class Concrete with abstract methods bar"
Exemple de https://dbader.org/blog/abstract-base- les classes-en-python
Edit: pour inclure la syntaxe python3, @PandasRocks remercie
Il fera 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 profonde en territoire « ennemi » en raison du non-support beaucoup plus facile.
faire de la méthode abstraite sûr que ce soit la méthode que vous appelez dans la classe parent doit être apparaître dans la classe des enfants. Voici noraml façon d'appeler à l'aide et abstraite. Le programme écrit dans python3
façon normale d'appeler
class Parent:
def methodone(self):
raise NotImplemented()
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
c.methodone()
'methodone () est appelée'
c.methodtwo()
NotImplementedError
Avec la méthode abstraite
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
c = Son()
TypeError. Instanciation classe abstraite Fils avec des méthodes abstraites methodtwo
Depuis methodtwo n'est pas appelé dans la classe des enfants que nous avons eu erreur. La mise en œuvre est inférieure à
from abc import ABCMeta, abstractmethod
class Parent(metaclass=ABCMeta):
@abstractmethod
def methodone(self):
raise NotImplementedError()
@abstractmethod
def methodtwo(self):
raise NotImplementedError()
class Son(Parent):
def methodone(self):
return 'methodone() is called'
def methodtwo(self):
return 'methodtwo() is called'
c = Son()
c.methodone()
'methodone () est appelée'