Les métaclasses en Python : contrôler la création des classes

En Python, tout est un objet, y compris les classes elles-mêmes. Les métaclasses sont les classes qui définissent comment les autres classes sont créées. L'objet intégré type est la métaclass par défaut en Python, permettant une personnalisation avancée du mécanisme de définition des classes.

Pour vérifier le type d'un objet, on utilise type() :

print(type(42))
print(type("hello"))
print(type([1, 2, 3]))

Dans un exemple simple, définisons une classe et observons son type :

class Personne:
    def __init__(self, nom):
        self.nom = nom

p1 = Personne(nom="Alice")
print(type(p1))  # Affiche <class '__main__.Personne'>
print(type(Personne))  # Affiche <class 'type'>

Cela montre que les classes standard, comme dict ou list, sont des instances de type, qui agit comme une métaclass.

Création de classes avec type

Les classes peuvent être définies de deux manières principales : via le mot-clé class ou en appelant directement type.

Utilisation du mot-clé class

class Animal:
    def __init__(self, espece):
        self.espece = espece

print(type(Animal))  # <class 'type'>

Utilisation de type

La fonction type peut créer dynamiquement une classe. Sa signature est type(nom, bases, espace).

def crier(self):
    print(f"{self.espece} crie!")

Chien = type("Chien", (Animal,), {"crier": crier})
print(type(Chien))  # <class 'type'>
print(Chien.__dict__)

# Équivalent avec la syntaxe class :
class Chat(Animal):
    crier = crier

print(type(Chat))
print(Chat.__dict__)

Les deux approches produisent des dictionnaires d'attributs similaires pour les classes créées.

Rôle des métaclasses

Les classes contrôlent la création d'objets, tandis que les métaclasses contrôlent la création de classes, permettant de personnaliser leurs propriétés.

Implémentation d'une métaclass personnalisée

Imposons une contrainte : tous les noms de classes doivent être en majuscules.

Création de la métaclass

class MetaClassePersonnalisee(type):
    def __init__(cls, nom, bases, espace):
        if not nom.isupper():
            raise TypeError(f"Le nom de la classe {nom} doit être en majuscules.")
        super().__init__(nom, bases, espace)

# Utilisation :
class UTILISATEUR(metaclass=MetaClassePersonnalisee):
    pass

# Cela lèverait une erreur :
# class utilisateur(metaclass=MetaClassePersonnalisee): ...

Personnalisation de la création d'objets

Pour contrôler l'instanciation, on redéfinit __call__ dans la métaclass.

class MetaClasseStricte(type):
    def __call__(cls, *args, **kwargs):
        if args:
            raise TypeError("Les paramètres doivent être nommés.")
        instance = super().__call__(*args, **kwargs)
        return instance

class Configuration(metaclass=MetaClasseStricte):
    def __init__(self, parametre):
        self.parametre = parametre

# Correct :
config1 = Configuration(parametre="valeur")
# Incorrect :
# config2 = Configuration("valeur")  # TypeError

Méthodes __new__ et __call__

La méthode __new__ crée le squelette de l'objet, tandis que __init__ initialise ses attributs. Dans une métaclass, __new__ et __init__ gèrent la classe elle-même.

class MetaClasseAvancee(type):
    def __new__(cls, nom, bases, espace):
        print("Création de la classe avec __new__")
        return super().__new__(cls, nom, bases, espace)

    def __init__(cls, nom, bases, espace):
        print("Initialisation de la classe avec __init__")
        super().__init__(nom, bases, espace)

    def __call__(cls, *args, **kwargs):
        print("Appel de la classe via __call__")
        return super().__call__(*args, **kwargs)

class Produit(metaclass=MetaClasseAvancee):
    def __init__(self, nom):
        self.nom = nom

p = Produit("Livre")  # Déclenche les méthodes de la métaclass

Lors de l'instanciation, la séquence est : métaclass __call__ → classe __new__ → classe __init__. Évitez d'appeler explicitement __init__ dans __new__ pour prévenir les doubles appels.

Étiquettes: Python metaclass type class object-oriented-programming

Publié le 16 juillet à 11h31