Les métaclasses en Python sont des classes spéciales qui créent et gèrent d'autres classes. Puisque tout est objet en Python, y compris les classes, les métaclasses peuvent être considérées comme des "classes pour les classes". Elles offrent un mécanisme pour contrôler le processus de création des classes et modifier leur comportement au moment de leur définition.
Principe de fonctionnement des métaclasses
Lorsque Python rencontre une définition de classe, il utilise automatiquement la métaclasse type pour créer cette classe. Le processus se déroule comme suit :
class MaClasse:
pass
En interne, Python exécute :
MaClasse = type('MaClasse', (), {})
Où :
'MaClasse'est le nom de la classe()est le tuple des classes parentes{}est le dictionnaire des attributs et méthodes de la classe
La métaclasse intégrée type est responsable de la création de toutes les classes en Python par défaut.
Création de métaclasses personnalisées
Les métaclasses personnalisées sont créées en héritant de type et en redéfinissant ses méthodes :
class MaMeta(type):
def __new__(cls, nom, bases, attrs):
print(f"Création de la classe {nom}")
attrs['cree_par_metaclasse'] = True
return super().__new__(cls, nom, bases, attrs)
class MaClasse(metaclass=MaMeta):
pass
print(MaClasse.cree_par_metaclasse) # True
Méthodes clés des métaclasses
__new__
Gère la création de la classe. Appelée avant __init__, elle retourne la nouvelle classe :
class MetaCreation(type):
def __new__(cls, nom, bases, attrs):
# Modification des attributs avant la création de la classe
attrs['id_classe'] = id(cls)
return super().__new__(cls, nom, bases, attrs)
__init__
Initialise la classe déjà créée :
class MetaInit(type):
def __init__(cls, nom, bases, attrs):
print(f"Initialisation de la classe {nom}")
super().__init__(nom, bases, attrs)
__call__
Définit le comportement lors de la création d'instances de la classe :
class MetaAppel(type):
def __call__(cls, *args, **kwargs):
print(f"Création d'une instance de {cls.__name__}")
return super().__call__(*args, **kwargs)
Exemples pratiques d'utilisation
Conversion automatique des attributs
class MetaMajuscule(type):
def __new__(cls, nom, bases, attrs):
attrs_maj = {}
for nom_attr, valeur_attr in attrs.items():
if not nom_attr.startswith('__'):
attrs_maj[nom_attr.upper()] = valeur_attr
else:
attrs_maj[nom_attr] = valeur_attr
return super().__new__(cls, nom, bases, attrs_maj)
class MaClasse(metaclass=MetaMajuscule):
attr1 = 'valeur1'
attr2 = 'valeur2'
print(hasattr(MaClasse, 'ATTR1')) # True
print(hasattr(MaClasse, 'attr1')) # False
Implémentation du pattern Singleton
class MetaSingleton(type):
_instances = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class ConnexionBase(metaclass=MetaSingleton):
def __init__(self):
print("Création de la connexion à la base de données")
db1 = ConnexionBase()
db2 = ConnexionBase()
print(db1 is db2) # True
Validation de la structure des classes
class MetaValidee(type):
def __new__(cls, nom, bases, attrs):
# Validation : s'assurer qu'une méthode spécifique est présente
if 'methode_requise' not in attrs:
raise TypeError(f"La classe {nom} doit définir 'methode_requise'")
return super().__new__(cls, nom, bases, attrs)
class MaClasseValidee(metaclass=MetaValidee):
def methode_requise(self):
pass