Cours
Une métaclasse en Python est une classe de classe qui définit le comportement d’une classe. Une classe est elle-même une instance d’une métaclasse. Une classe en Python définit comment se comporteront les instances de cette classe. Pour bien comprendre les métaclasses, il faut déjà être à l’aise avec les classes Python. Avant d’entrer dans le vif du sujet, clarifions quelques notions.
Tout est objet en Python
class TestClass():
pass
my_test_class = TestClass()
print(my_test_class)
<__main__.TestClass object at 0x7f6fcc6bf908>
Les classes Python peuvent être créées dynamiquement
type en Python permet d’obtenir le type d’un objet. Vérifions le type de l’objet créé ci-dessus.
type(TestClass)
type
type(type)
type
Attendez, que s’est-il passé ? On s’attendrait à ce que le type de l’objet créé soit class, mais ce n’est pas le cas. Gardez cela en tête, nous y revenons plus loin. On remarque aussi que le type de type est type lui-même. C’est une instance de type. Autre particularité de type : il permet de créer des classes dynamiquement. Voyons comment faire. La classe DataCamp ci-dessous peut être créée avec type de la manière suivante :
class DataCamp():
pass
DataCampClass = type('DataCamp', (), {})
print(DataCampClass)
print(DataCamp())
<class '__main__.DataCamp'>
<__main__.DataCamp object at 0x7f6fcc66e358>
Dans l’exemple ci-dessus, DataCamp est le nom de la classe tandis que DataCampClass est la variable qui référence la classe. Avec type, on peut passer des attributs de classe via un dictionnaire comme ci-dessous :
PythonClass = type('PythonClass', (), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass.start_date, PythonClass.instructor)
print(PythonClass)
August 2018 John Doe
<class '__main__.PythonClass'>
Si nous voulons que PythonClass hérite de la classe DataCamp, on la passe comme second argument lors de la définition avec type
PythonClass = type('PythonClass', (DataCamp,), {'start_date': 'August 2018', 'instructor': 'John Doe'} )
print(PythonClass)
<class '__main__.PythonClass'>
Maintenant que ces deux notions sont posées, on réalise que Python crée les classes à l’aide d’une métaclasse. Nous avons vu que tout est objet en Python ; ces objets sont créés par des métaclasses. Chaque fois que l’on utilise class pour définir une classe, une métaclasse fait la magie en coulisses. Nous avons déjà vu type le faire en pratique. C’est comparable à str qui crée des chaînes et à int qui crée des entiers. En Python, l’attribut ___class__ permet d’obtenir le type de l’instance courante. Créons une chaîne et vérifions son type.
article = 'metaclasses'
article.__class__
str
On peut également vérifier le type avec type(article).
type(article)
str
Si l’on vérifie le type de str, on constate aussi que c’est type.
type(str)
type
Si l’on vérifie le type de float, int, list, tuple et dict, on obtient le même résultat. Car tous ces objets sont de type type.
print(type(list),type(float), type(dict), type(tuple))
<class 'type'> <class 'type'> <class 'type'> <class 'type'>
Nous avons vu que type crée des classes. Donc, si l’on consulte le __class__ de __class__, cela devrait renvoyer type.
article.__class__.__class__
type
Maîtrisez vos compétences en matière de données avec DataCamp
Plus de 10 millions de personnes apprennent Python, R, SQL et d'autres compétences techniques grâce à nos cours pratiques élaborés par des experts du secteur.

Créer des métaclasses personnalisées
En Python, on peut personnaliser le processus de création d’une classe en passant le mot-clé metaclass dans la définition de la classe. On peut aussi l’obtenir par héritage si la classe parente définit déjà ce mot-clé.
class MyMeta(type):
pass
class MyClass(metaclass=MyMeta):
pass
class MySubclass(MyClass):
pass
On voit ci-dessous que le type de la classe MyMeta est type et que le type de MyClass et MySubClass est MyMeta.
print(type(MyMeta))
print(type(MyClass))
print(type(MySubclass))
<class 'type'>
<class '__main__.MyMeta'>
<class '__main__.MyMeta'>
Lorsqu’aucune métaclasse n’est spécifiée lors de la définition d’une classe, la métaclasse par défaut type est utilisée. Si une métaclasse est fournie et qu’elle n’est pas une instance de type(), elle est utilisée directement comme métaclasse.
__new__ et __init__
On peut aussi définir des métaclasses de l’une des deux manières suivantes. Nous expliquons ensuite la différence.
class MetaOne(type):
def __new__(cls, name, bases, dict):
pass
class MetaTwo(type):
def __init__(self, name, bases, dict):
pass
__new__ s’utilise lorsqu’on veut définir le dict ou le tuple bases avant la création de la classe. La valeur de retour de __new__ est généralement une instance de cls. __new__ permet aux sous-classes de types immuables de personnaliser la création d’instances. Il peut être surchargé dans des métaclasses personnalisées pour adapter la création de classe. __init__ est en général appelé après la création de l’objet afin de l’initialiser.
Méthode __call__ de métaclasse
Selon la documentation officielle, on peut aussi surcharger d’autres méthodes de classe en définissant une méthode __call__() personnalisée dans la métaclasse afin d’introduire un comportement spécifique lors de l’appel de la classe.
Méthode __prepare__ de métaclasse
D’après le modèle de données Python
Une fois la métaclasse appropriée identifiée, l’espace de noms de la classe est préparé. Si la métaclasse possède un attribut __prepare__, il est appelé comme suit : namespace = metaclass.__prepare__(name, bases, **kwds) (les arguments nommés additionnels, le cas échéant, proviennent de la définition de la classe). Si la métaclasse n’a pas d’attribut __prepare__, l’espace de noms de la classe est initialisé comme un mapping ordonné vide. - docs.python.org
Conception d’un singleton avec une métaclasse
Il s’agit d’un patron de conception qui limite l’instanciation d’une classe à un seul objet. Cela peut être utile, par exemple, pour une classe de connexion à une base de données, où l’on souhaite n’avoir qu’une seule instance de la connexion.
class SingletonMeta(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super(SingletonMeta,cls).__call__(*args, **kwargs)
return cls._instances[cls]
class SingletonClass(metaclass=SingletonMeta):
pass
Conclusion
Dans cet article, nous avons vu ce que sont les métaclasses et comment les implémenter dans nos programmes Python. Les métaclasses peuvent servir, entre autres, à la journalisation, à l’enregistrement des classes à la création ou encore au profilage. Le concept peut sembler assez abstrait et vous vous demandez peut-être si vous en avez réellement besoin. Le vétéran de Python, Tim Peters, y répond mieux que quiconque.
"Les métaclasses relèvent d’une magie plus profonde que ce dont 99 % des utilisateurs devraient se préoccuper. Si vous vous demandez si vous en avez besoin, la réponse est non (ceux qui en ont vraiment besoin le savent avec certitude et n’ont pas besoin d’une explication du pourquoi)." - realpython
Pour aller plus loin en Python, commencez le parcours de compétences Python Programming sur DataCamp et consultez notre tutoriel sur les classes Python.