Langage Python

Métaprogrammation en Python

L'une des techniques les plus importantes du développement logiciel est la suivante: « Ne vous répétez pas le code .» C'est-à-dire que, chaque fois que vous rencontrez un problème de création de code hautement répétitif (ou de couper ou coller du code source), il est souvent avantageux de rechercher un fichier. solution plus élégante.
En Python, ces problèmes sont souvent résolus sous la catégorie «métaprogrammation». En bref, la métaprogrammation consiste à créer des fonctions et des classes dont le but principal est de manipuler du code (par exemple, modifier, générer ou encapsuler du code existant). Les principales caractéristiques de ceci incluent les décorateurs, les décorateurs de classe et les métaclasses.

Décorateurs

CoursDécorateurs en python

Métaclasses

En Python, tout est associé à un type. Par exemple, si nous avons une variable ayant une valeur entière, son type est int.
Vous pouvez obtenir le type de n'importe quoi en utilisant la fonction type().

num = 23
print("Type de num est :", type(num))

liste = [1, 2, 4]
print("Type de liste est :", type(liste))

nom = "ESSADDOUKI"
print("Type de nom est :", type(nom))
 class="brush: python"
Type de num est : < class 'int' >
Type de liste est : < class 'list' >
Type de nom est : < class 'str' >

Chaque type en Python est défini par une classe. Ainsi, dans l'exemple ci-dessus, contrairement à C ou Java où int, char, float sont des types de données primaires, en Python, ils sont des objets de classe int ou de classe str.
Nous pouvons donc créer un nouveau type en créant une classe de ce type.
Par exemple, nous pouvons créer un nouveau type Personne en créant une classe Personne.

    class Personne:
        pass
    
    p=Personne()
    print("Type de p est :", type(p))
Type de p est : < class '__main__.Personne'>

Une classe est également un objet et, comme tout objet, elle est une instance de quelque chose appelé Metaclass. Un type de classe spécial crée ces objets de classe. La classe type est la métaclasse par défaut qui est responsable de la création des classes.
Par exemple, dans l'exemple ci-dessus, si nous essayons de déterminer le type de classe Personne, il s'agit d'un type.

class Personne:
    pass

print("Type de Personne est :", type(Personne))
Type de Personne est : < class 'type' >

Les classes étant également un objet, elles peuvent être modifiées de la même manière. Nous pouvons ajouter ou soustraire des champs ou des méthodes en classe de la même manière que nous le faisions avec d'autres objets.
Par exemple

class Personne:
    pass


Personne.nom = "ESSADDOUKI"
Personne.affiche = lambda self: print(self.nom)

p = Personne()
p.affiche()
ESSADDOUKI

Toute cette procédure peut être résumée comme suit : Métaclasse crée des classes et les classes créent des objets

Une métaclasse est responsable de la génération des classes. Nous pouvons donc écrire nos propres métaclasses personnalisées pour modifier la façon dont les classes sont générées en effectuant des actions supplémentaires ou en injectant du code. Généralement, nous n’avons pas besoin de métaclasses personnalisées, mais cela est parfois nécessaire.

Créer une métaclasse personnalisée :

Pour créer notre métaclasse personnalisée, notre métaclasse personnalisée doit hériter du métaclasse type et généralement réimplementer les méthodes suivantes :

  • __new __ (): c’est une méthode appelée avant __init __ (). Il crée l'objet et le retourne. Nous pouvons remplacer cette méthode pour contrôler la manière dont les objets sont créés.
  • __init __ (): cette méthode initialise simplement l'objet créé passé en paramètre

Nous pouvons créer des classes en utilisant directement la fonction type(). Il peut être appelé de différentes manières:

  • Appelé avec un seul argument, il retourne le type.
  • Appelé avec trois paramètres, il crée une classe. Les arguments suivants lui sont transmis :
    • Nom de la classe
    • Tuple ayant des classes de base héritées par la classe
    • Dictionnaire de classe: Il sert d’espace de nom local pour la classe, contient les méthodes de classe et les attributs.
def TEST(self):
    print("je suis : ", self.__class__.__name__)


class Personne:
    def Showme(self):
        print("je suis une personne")


Test = type('Meta', (Personne, ), dict(
    nom="ESSADDOUKI", prenom="Mostafa", affiche=TEST))

# Afficher le type de TEST
print("Type de TEST est : ", type(Test))

# Créer une instance de la classe TEST
p = Test()
print("Type de p: ", type(p))

p.affiche()
p.Showme()
print("Mon nom est ", p.nom)
        
Type de TEST est :  < class 'type'>
Type de p:  < class '__main__.Meta'>
je suis :  Meta
je suis une personne
Mon nom est  ESSADDOUKI
        

Créons maintenant une métaclasse sans utiliser directement type(). Dans l'exemple suivant, nous allons créer une métaclasse HeritageMultiple qui vérifiera si la classe en cours de création a hérité de plusieurs classes de base. Si c'est le cas, une erreur sera générée.

class HeritageMultipe(type):

    #cls : type classe
    # clsname : nom de la classe
    # bases : classes de base
    #clsdict : liste des attributs et méthodes
    def __new__(cls, clsname, bases, clsdict):
        # si il hérite de plusieurs classes déclencher une erreur
        if len(bases) > 1:
            raise TypeError("Héritage multiple est non autorisé!!!")

        # sinon exécuter __new__ de la classe mère (type)
        return super().__new__(cls, clsname, bases, clsdict)

# définir une métaclass en utilisant le mot metaclass
# maintenant la classe HeritageMultiple est utilisée pour
# la création des classes
class Base(metaclass=HeritageMultipe):
    pass


class A(Base):
    pass


class B(Base):
    pass

# Cela déclenchera une erreur, parce que elle hérite de deux classes A et B
class C(A, B):
    pass
        
Traceback (most recent call last):
    File "meta.py", line 34, in 
        class C(A, B):
    File "meta.py", line 10, in __new__
        raise TypeError("Héritage multiple est non autorisé!!!")
    TypeError: Héritage multiple est non autorisé!!!

Quand utiliser les métaclasses

La plupart du temps, nous n'utilisons pas de métaclasse, elles sont comme de la magie noire et servent généralement à quelque chose de compliqué, mais peu de cas où nous utilisons des métaclasses sont

  • Comme nous l'avons vu dans l'exemple ci-dessus, les métaclasses se propagent dans les hiérarchies d'héritage. Cela affectera également toutes les sous-classes. Si nous avons une telle situation, alors nous devrions utiliser des métaclasses.
  • Si nous voulons changer de classe automatiquement quand il est créé
  • Si vous êtes développeur d'APIs, vous pouvez utiliser des métaclasses.
Contrôler la création d'instances :

Si vous définissez une classe, vous l'appelez comme une fonction pour créer des instances. Si vous souhaitez personnaliser cette étape, vous pouvez le faire en définissant une métaclasse et en réimplémentant sa méthode __call __().
Pour illustrer cela, supposons que vous ne vouliez pas que quiconque crée des instances

            class Restriction(type):
                def __call__(self, *args, **kwargs):
                    raise TypeError("Ne peut pas instancier directement")
            
            # Exemple
            class TEST(metaclass=Restriction):
                pass
            
            obj = TEST()
        
            Traceback (most recent call last):
                File "meta.py", line 12, in < module>
                    obj = TEST()
                File "meta.py", line 3, in __call__
                    raise TypeError("Ne peut pas instancier directement")
                TypeError: Ne peut pas instancier directement
        

L'utilisation d'une métaclasse pour implémenter divers modèles de création d'instances peut souvent constituer une approche beaucoup plus élégante que d'autres solutions n'impliquant pas de métaclasses.

Capture de l'ordre de définition d'attribut de classe :

Vous souhaitez enregistrer automatiquement l'ordre dans lequel les attributs et les méthodes sont définis dans un corps de classe afin de pouvoir l'utiliser dans diverses opérations (par exemple, la sérialisation, la mise en correspondance avec des bases de données, etc.).

La capture d'informations sur le corps de la définition de classe est facilement réalisée grâce à l'utilisation d'une métaclasse. Voici un exemple de métaclasse qui utilise OrderedDict pour capturer l'ordre de définition des descripteurs:

                from collections import OrderedDict

                class Typed:
                    _expected_type = type(None)
                
                    def __init__(self, name=None):
                        self._name = name
                
                    # redéfinir la mathode d'affectation __set__
                    # permet de comparer la valeur de l'attribut et le type d'attribut
                    def __set__(self, instance, value):
                        if not isinstance(value, self._expected_type):
                            raise TypeError('type attendu : ' + str(self._expected_type))
                
                        instance.__dict__[self._name] = value
                
                
                # définir les types attendus
                class Integer(Typed):
                    _expected_type = int
                
                class Float(Typed):
                    _expected_type = float
                
                class String(Typed):
                    _expected_type = str
                
                class OrderedMeta(type):
                
                    def __new__(cls, clsname, bases, clsdict):
                        # créer un dictionnaire à partir des attributs de la classe
                        d = dict(clsdict)
                
                        # liste contient l'ordre des attribut
                        ordre = []
                        for name, value in clsdict.items():
                
                            # si l'attribut est de type attendu
                            # ajouter dans ordre
                            if isinstance(value, Typed):
                                value._name = name
                                ordre.append(name)
                
                        # ajouter l'ordre dans le dictionnaire des attributs de classe
                        d['_order'] = ordre
                
                        # exécuter __new__ de la classe mère (type)
                        return type.__new__(cls, clsname, bases, d)
        

Dans cette métaclasse, l'ordre de définition des descripteurs est capturé à l'aide d'un ordre commandé lors de l'exécution du corps de la classe. L'ordre des noms résultant est ensuite extrait du dictionnaire et stocké dans un attribut de classe _order. Ceci peut ensuite être utilisé par les méthodes de la classe de différentes manières.
voici une classe simple Etudiant pour bien illustrer l'utilisation de la métaclasse OrderedMeta

            class Etudiant(metaclass=OrderedMeta):
                nom = String()
                prenom = String()
                age = Integer()
                note = Float()
            
                def __init__(self, nom, prenom, age, note):
                    self.nom = nom
                    self.prenom = prenom
                    self.age = age
                    self.note = note
            

            s = Etudiant('ESSADDOUKI', 'Mostafa', 31, 13.5)
            s = Etudiant('Kayouh', "mohamed", "30", 14.0)
        
            Traceback (most recent call last):
                File "meta.py", line 69, in < module>
                    s = Etudiant('Kayouh', "mohamed", "30", 14.0)
                File "meta.py", line 64, in __init__
                    self.age = age
                File "meta.py", line 14, in __set__
                    raise TypeError('type attendu : ' + str(self._expected_type))
                TypeError: type attendu : < class 'int'>
        

Partager ce cours avec tes amis :
Rédigé par Mostafa Sedoki
Professeur d'Informatique dans les CPGE

Cours Similaires :