En fait, pour répondre de façon plus générale à la question des métaclasses, celles-ci deviennent utiles à partir du moment où tu veux modifier une classe juste avant qu'elle ne soit définie.
Niveau fonctionnalité, ça peut ressembler à l'application d'un décorateur sur une classe. En fait, c'est sensiblement différent parce qu'un décorateur intervient juste après que l'objet qu'il décore ne soit défini. De plus, la métaclasse est héritée par les classes filles, à la différence d'un décorateur de classe qui ne s'applique qu'à son argument.
Prenons un exemple qui va nous permettre de voir comment se débrouille Python.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 | class MyMetaClass(type):
def __new__(cls, clsname, bases, dct):
print("MyMetaClass", clsname, dct)
# Ici on peut modifier le nom que portera la classe
# Ou bien lui rajouter ou lui supprimer des classes mères
# Ou bien modifier ses attributs de classe ou ses méthodes
return super().__new__(cls, clsname, bases, dct)
def decorator(obj):
print(obj, "was just defined")
return obj
@decorator
class MyClass(metaclass=MyMetaClass):
@decorator
def __new__(cls, *args):
print("MyClass.__new__", *args)
return super().__new__(cls)
@decorator
def __init__(self, bar):
print("MyClass.__init__", bar)
self.bar = bar
a = MyClass("foo")
|
À l'exécution, ça donne :
| <function MyClass.__new__ at 0x7f35df171bf8> was just defined
<function MyClass.__init__ at 0x7f35df171c80> was just defined
MyMetaClass MyClass {'__qualname__': 'MyClass', '__module__': '__main__', '__init__': <function MyClass.__init__ at 0x7f35df171c80>, '__new__': <function MyClass.__new__ at 0x7f35df171bf8>}
<class '__main__.MyClass'> was just defined
MyClass.__new__ foo
MyClass.__init__ foo
|
Dans l'ordre :
- Python a compilé et défini la méthode
__new__
de la classe MyClass
. Cette méthode est la toute première que Python appelle pour instancier un objet.
- Python a compilé et défini la méthode
__init__
de la classe MyClass
.
- C'est là que c'est intéressant. Il n'a même pas encore fini de définir la classe
MyClass
qu'il instancie MyMetaClass
avec trois arguments :
- le nom de la classe
MyClass
dont MyMetaClass
est la méta-classe.
- le tuple des classes dont hérite
MyClass
(en dehors de object
). Ici il n'y en a aucune.
- le dictionnaire des attributs de
MyClass
Cet appel à __new__
est chargé de construire la classe. En gros, puisque tout est objet en Python, ça revient à instancier la classe type
. Cela veut dire que dans cette méthode, on peut modifier la classe elle-même avant qu'elle ne soit créée (ou même après, du moment que l'on retourne la classe à la fin). Par exemple on peut lui rajouter des méthodes ou des attributs dynamiquement.
- Une fois que
MyMetaClass.__new__()
retourne, l'objet résultant (qui est une classe), est enregistrée dans le scope courant sous le nom MyClass
.
On en arrive à la dernière ligne du script : qui illustre simplement le fait que __new__
soit le constructeur à proprement parler de la classe, puisqu'il est appelé avant __init__
.