Participer au site avec un Tip
Rechercher
 

Améliorations / Corrections

Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.

Emplacement :

Description des améliorations :

Intercepter les accès aux attributs de la classe

Accès rapide :
Intercepter les accès en lecture
Intercepter les accès en écriture
Revisitons l'encapsulation en Python

Intercepter les accès en lecture

On le sait, ou pas, mais en fait un objet Python est implémenté en interne sous forme d'un dictionnaire. C'est pour cela que vos objets Python peuvent être enrichis à n'importe quel moment. Mais c'est pire que cela : on peut aussi définir des pseudo-attributs que ne seront pas stockés dans le dictionnaire sous-jacent. Pour avoir accès, en lecture, à ces pseudo-attributs, il faut rajouter à la classe considérée la méthode __getattr__.

si l'attribut existe réellement sur l'instance, la méthode __getattr__ ne sera pas invoquée.

Voici un petit exemple pour vous en convaincre.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
#!/usr/bin/python3


class Interceptor:
    
    def __init__(self):
        self.initialAttribut = "without __getattr__ call"
    
    def __getattr__(self, name):
        print(f"In __getattr__ with {name}")
        return name + " required"
      
      
if __name__ == '__main__': 
    instance = Interceptor()
    print(instance.initialAttribut)
    print(instance.pseudoAttribut)
Fichier Interceptor.py

Pour lancer cet exemple, veuillez procéder ainsi :

$> python3 Interceptor.py 
without __getattr__ call
In __getattr__ with pseudoAttribut
pseudoAttribut required
$>

Intercepter les accès en écriture

Il est aussi possible d'avoir accès à ces pseudo-attributs en écriture : dans ce cas, la méthode à redéfinir se nomme __setattr__. Dans l'exemple proposé ci-dessous, je crée dans le dictionnaire sous-jacent une entrée correpondant à l'attribut souhaité, à condition que son nom commence par t (pourquoi pas) : du coup, l'attribut existe maintenant bel et bien et on peut ensuite y accéder en direct. Les autres pseudo-attributs (ne commençant pas par t) déclencheront une exception.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
#!/usr/bin/python3

class Interceptor:
    
    def __setattr__(self, name, value):
        print(f"In __setattr__ with {name} = {value}")
        if name.startswith("t"):
            #super().__setattr__(name, value)
            self.__dict__[name] = value       
        else:
            raise AttributeError(name + " attribute not supported")
         
                 
if __name__ == '__main__':
     
    inter = Interceptor()
    inter.test = 10
    print(inter.test)
     
     
    inter.other = "no :-("
Fichier Interceptor.py

Pour lancer cet exemple, veuillez procéder ainsi :

$> python3 Interceptor.py 
In __setattr__ with test = 10
10
In __setattr__ with other = no :-(
Traceback (most recent call last):
  File "/home/dominique/Documents/My Trainings/Langages de programmation/Cours Python/PythonData/Exercices/UseMongo/Interceptor.py", line 36, in <module>
    inter.other = "no :-("
  File "/home/dominique/Documents/My Trainings/Langages de programmation/Cours Python/PythonData/Exercices/UseMongo/Interceptor.py", line 26, in __setattr__
    raise AttributeError(name + " attribute not supported")
AttributeError: other attribute not supported
$>

Revisitons l'encapsulation en Python

Comparé à certains langages de programmation comme Java ou C++, la notion de classe en Python est moins stricte : elle permet de rajouter autant d'attributs que souhaités sur la classe. Si, de ce point de vue, vous souhaitez vous rapprocher de la notion de classe en Java en interdisant l'ajout d'attribut non connus, vous pouvez utiliser ces mécanismes d'interception d'accès aux attributs.

Voici un petit exemple de mise en oeuvre.

 1 
 2 
 3 
 4 
 5 
 6 
 7 
 8 
 9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
#!/usr/bin/python3

class Rational:

    def __init__(self):
        print("in constructor")
        # On passe par le dictionnaire de l'objet pour ne pas forcer
        # un appel à la méthode __setattr__
        self.__dict__["numerator"] = 0
        self.__dict__["denominator"] = 1

    def __getattr__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]
        raise AttributeError("Attribute not supported for get")

    def __setattr__(self, key, value):
        if key in self.__dict__:
            if not isinstance(value, int):
                raise TypeError("value must be an integer")
            if key == "denominator" and value == 0:
                raise ValueError("denominator cannot be 0")
            self.__dict__[key] = value
        else:
            raise AttributeError("Attribute not supported for set")


# Test des attributs autorisés
obj = Rational()
obj.numerator = 4
obj.denominator = 3
print(obj.numerator, obj.denominator)

# Test des attributs non autorisés
# print(obj.a)
obj.truc = "Machin"
Fichier encap.py

Et voici les résultats produits par cet exemple de code :

$> python3 encap.py
in constructor
4 3
Traceback (most recent call last):
  File "/home/dominique/Documents/My Trainings/Langages de programmation/Cours Python/PythonData/Exercices/POO/TestAttr.py", line 35, in <module>
    obj.truc = "Machin"
  File "/home/dominique/Documents/My Trainings/Langages de programmation/Cours Python/PythonData/Exercices/POO/TestAttr.py", line 25, in __setattr__
    raise AttributeError("Attribute not supported for set")
AttributeError: Attribute not supported for set
$>