on a vu dans ce chapitre, et spécifiquement ici les mécanismes généraux de l’héritage en Python
mais ici encore, c’est la version simple des choses, car en bon langae objet qui se respecte,
Python nous permet aussi .. de modifier les règles de base de l’héritage (entre autres) !
c’est le propos des métaclasses, que nous allons étudier maintenant
à quoi ça sert ?¶
hum, bonne question ! en réalité on peut s’en servir pour énormément de choses! (même si, bien souvent il y a d’autres alternatives, dans la ménagerie de concepts Python, pour faire ces choses sans passer par les métaclasses...)
mais bon en tous cas c’est intéressant du point de vue conceptuel !
un prétexte: le singleton¶
pour quand même travailler sur une application concrète, nous allons illustrer le concept en implémentant la notion de singleton
c’est quoi ? on parle de singleton lorsqu’on veut qu’une classe n’existe qu’en un seul exemplaire dans l’application; comme un espèce d’objet global mais qui serait proprement partagé
par exemple: la configuration; ou une classe API pour dialoguer avec un autre programme; ou bien un cache pour ranger des données...
mais avant d’en arriver là nous allons devoir faire un peu de théorie...
type et object¶
jusqu’ici nous avons vu que
tout est objet en Python
tout objet a un type
une classe est un type - à partir d’ici on ne fera plus la distinction entre ces deux termes
une classe a aussi possiblement une ou plusieurs super-classes
relation entre objets et types¶
du coup on peut s’intéresser à la relation binaire entre les objets:
est une instance de ( est le type de )
# et pour ça on va se créer quelques classes et objets
class Class: pass
class SubClass(Class): pass
instance = SubClass()pour introspecter la relation “est une instance de”, c’est facile, on utilise type() !
# remontons la relation en partant de vector
type(instance), type(SubClass), type(Class), type(type)(__main__.SubClass, type, type, type)ce qui nous donne un premier élément de réponse:
un instance a comme type la classe qui l’a créée
une classe a comme type ..
type
ce qui nous donne le diagramme suivant
héritage entre classes¶
maintenant regardons quelle est la relation entre classes et super-classes:
hérite directement ( a comme classe de base )
la question ne se pose pas à propos de vector (seules les classes ont des super-classes)
mais pour les autres on obtient ceci - en utilisant l’attribut spécial __bases__
# the base classes of our classes
SubClass.__bases__, Class.__bases__((__main__.Class,), (object,))# the base classes of the builtin classes
object.__bases__, type.__bases__((), (object,))donc on obtient, si on superpose les deux relations:
comment les objets sont-ils créés ?¶
une fois que ceci est bien clair, voyons un peu plus en détail comment les objets sont créés en Python
c’est-à-dire quand j’appelle par exemple Class(0):
l’objet
Classest donc un callable, on va chercher son attribut__call__comme il s’agit d’une utilisation implicite de dunder, on ne cherche pas dans l’espace de nom de
Person, mais dans sa classeor on vient de le voir, sa classe c’est
type; effectivement il existe une méthodetype.__call__()qui fait - en gros - ceci (credits)
# the actual code is in C, but a pseudo implementation
# of the type class in Python could look like this
class type:
# when calling Class(0), we will call this method with
# cls = Class
# args = (0,)
# kwargs = {}
def __call__(cls, *args, **kwarg):
# ... a few things could possibly be done to cls here... maybe... or maybe not...
# then we call cls.__new__() to get a new object
# think of this as a class method (the object has not been created yet...)
obj = cls.__new__(cls, *args, **kwargs)
# ... a few things done to obj here... maybe... or not...
# then we call obj.__init__()
obj.__init__(*args, **kwargs)
# ... maybe a few more things done to obj here
# then we return obj
return objc’est-à-dire en français, on crée un objet en passant par deux étapes, qui sont chacune liée à une dunder:
__new__()une méthode de classe (car on n’a pas encore créé l’objet) dans l’esprit, elle est chargée d’allouer la mémoire__init__()une méthode usuelle chargée d’initialiser l’objet
le cas d’un objet usuel¶
on peut donc poursuivre notre analyse, on est toujours en train d’évaluer Class(0)
au moment d’appeler __new__ on la cherche à partir de l’objet Class
qui en général n’a pas redéfini cette méthode
on
pour en savoir plus¶
cette section de la doc officielle donne tous les détails
https://