Attributs, propriétés et descripteurs en Python

En Python, un attribut de classe est défini au niveau de la classe et partagé par toutes ses instances. Un attribut d'instance est propre à chaque objet créé à partir de la classe.

Les différences principales :

  • Stockage : l'attrbiut de classe réside dans l'espace de noms de la classe, l'attribut d'instance dans celui de l'objet.
  • Accès : un attribut de classe s'atteint par le nom de la classe ou par une instance ; un attribut d'instance uniquement par l'instance.
  • Héritage : les sous-classes héritent des attributs de classe, pas des attrbiuts d'instance.
class Exemple:
    valeur_classe = "partagé"

    def __init__(self, val):
        self.valeur_instance = val

obj1 = Exemple("A")
obj2 = Exemple("B")
print(Exemple.valeur_classe)      # 'partagé'
print(obj1.valeur_classe)         # 'partagé'
print(obj1.valeur_instance)       # 'A'
print(obj2.valeur_instance)       # 'B'

  • Attribut calculé : transformer une méthode en attribut en lecture seule dont la valeur est déterminée dynamiquement.
  • Validation de données : dans le setter, vérifier que la valeur respecte une contrainte.
  • Protection en écriture : rendre un attribut accessible seulement en lecture.
  • Encapsulation : masquer la logique interne tout en offrant une interface simple.

Un descripteur est une classe qui implémente une ou plusieurs des méthodes __get__, __set__ et __delete__. Il permet un contrôle fin de l'accès aux attributs. property est un raccourci intégré qui appelle des méthodes getter/setter/deleteur.

Exemple de descripteur limitant une plage de valeurs :

class Plage:
    def __init__(self, mini, maxi):
        self.mini = mini
        self.maxi = maxi

    def __set_name__(self, proprietaire, nom):
        self.nom = nom

    def __get__(self, instance, proprietaire):
        return instance.__dict__[self.nom]

    def __set__(self, instance, valeur):
        if valeur < self.mini or valeur > self.maxi:
            raise ValueError("Hors plage")
        instance.__dict__[self.nom] = valeur

class Cercle:
    rayon = Plage(0, 100)

c = Cercle()
c.rayon = 50
print(c.rayon)   # 50
# c.rayon = 200  # ValueError

Même fonction avec property :

class Cercle2:
    def __init__(self, rayon):
        self._rayon = rayon

    @property
    def rayon(self):
        return self._rayon

    @rayon.setter
    def rayon(self, valeur):
        if valeur < 0 or valeur > 100:
            raise ValueError("Hors plage")
        self._rayon = valeur

La propriété est plus simple à écrire pour un cas standard, le descripteur plus adapté quand la même logique doit être réutilisée sur plusieurs attributs ou classes.

Un descripteur intercepte les accès à un attribut dès qu'il est défini comme attribut de classe. Les méthodes __get__, __set__ et __delete__ sont appelées automatiquement lors de la lecture, l'écriture ou la suppression de l'attribut sur une instance.

Exemple : competur d'accès à un attribut.

class CompteurAcces:
    def __init__(self, nom):
        self.nom = nom
        self.compteur = 0

    def __get__(self, instance, proprietaire):
        self.compteur += 1
        return getattr(instance, self.nom)

    def __set__(self, instance, valeur):
        self.compteur += 1
        setattr(instance, self.nom, valeur)

    def __delete__(self, instance):
        self.compteur += 1
        delattr(instance, self.nom)

class Test:
    x = CompteurAcces('x')

t = Test()
t.x = 42          # compteur = 1
print(t.x)        # compteur = 2, affiche 42
del t.x           # compteur = 3

Étiquettes: Python property descripteur attribut de classe attribut d'instance

Publié le 17 juin à 22h37