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