Une métaclasse en python permet de définir le comportement de l'objet enfant sur lequel il est défini. On retrouve la spécification sur la PEP 3115
Si on prend une classe vide et que l'on analyse son type
Copier le code
1 2 3 4 5 class MyClass(): pass type(MyClass) <class 'type'>
On remarque que MyClass est de type
typeIl hérite de la métaclasse type. Donc pour créer une nouvelle métaclasse on fait
Copier le code
1 2 3 4 5 class MyMeta(type): pass type(MyMeta) <class 'type'>
Et pour appliquer cette metaclasse à une classe on la déclare ainsi
Copier le code
1 2 3 4 5 class MyClass(metaclass=MyMeta): pass type(MyClass) <class '__main__.MyMeta'>
metaclass permet d'agir sur le dunder __new__ qui est appelé avant la création de l'objet.
Nous avons également vu qu'il doit hériter de type qui prend 3 paramètres :
- name: nom de la classe
- bases: tuple des classes parentes
- attrs: dictionaire contenant les noms et valeurs des attributs
Donc si on veut simuler la création de notre instance MyClass depuis la métaclasse on fait
return super().__new__(cls, name, bases, dict)
Ce qui nous donnerait
Copier le code
1 2 3 4 5 6 7 8 9 10 11 class MyMeta(type): def __new__(cls, name, bases, attrs): return super().__new__(cls, name, bases, attrs) class MyClass(metaclass=MyMeta): pass >>> type(MyMeta) <class 'type'> >>> type(MyClass) <class '__main__.MyMeta'>
Jusque là rien de bien nouveau. Mais si on veut modifier notre instance en ajoutant par exemple un attribut qui correspond à la date de création, on ferait
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from datetime import datetime class MyMeta(type): def __new__(cls, name, bases, attrs): obj = super().__new__(cls, name, bases, attrs) obj.created_date = datetime.now() return obj class MyClass(metaclass=MyMeta): pass >>> type(MyMeta) <class 'type'> >>> type(MyClass) <class '__main__.MyMeta'> >>> print(MyClass().created_date) 2022-09-07 14:00:07.867773
Ce mécanisme pourrait très bien être implémenté avec de l'héritage simple, c'est à vous de déterminer ce qui est le plus adapté en fonction de votre besoin et du contexte.
On pourrait très bien imaginer une métaclasse qui associe un attribut à un logger
Copier le code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import logging class MyMeta(type): def __new__(cls, name, bases, attrs): obj = super().__new__(cls, name, bases, attrs) obj.log = logging.getLogger() return obj class MyClass(metaclass=MyMeta): def __init__(self): self.log.warning('Instance created...') MyClass() >>> Instance created...
On vient de voir comment type permet de créer des instances de classe et comment vous pouvez vous aussi personnaliser ces objets