modifié le

Membres de classe

classe

Les membres (c’est-à-dire les attributs et les méthodes) ont été définis jusqu’ici au niveau des instances. Mais on peut en définir aussi au niveau d’une classe.

1. Attribut de classe

Contrairement à un attribut d’instance, propre à chaque instance, un attribut de classe est partagé par toutes les instances de la classe.

Exemple : on voudrait que le classe Fraction ait 2 comportements distincts au choix : soit les instances se simplifient automatiquement, soit elles restent inchangés. On veut décider en début de programme si l’on veut que les fractions se simplifient ou pas. Cette propriété s’applique alors à l’ensemble de l’application, pour chaque instance. Ce qui veut dire que si la classe est en mode simplification, alors toutes instances du programme vont se simplifier.

Pour cela, on va utiliser un attribut de classe (en l’occurence ici reduction) pour mémoriser le mode de fonctionnement courant :

from math import gcd

class Fraction:
  reduction = True (1)

  def __init__(self,n,d):
    self._num = n
    self._den = d
    if Fraction.reduction: (2)
      self.reduire()

  def switch_reduction(self):
    Fraction.reduction = not Fraction.reduction (2)

  def reduire(self):
    if self._den==0:
      raise Exception("le dénominateur est nul")
    pgcd = gcd(self._num,self._den)
    self._num //= pgcd
    self._den //= pgcd

  def __str__(self):
    return f"{self._num}/{self._den}"
1 reduction est un attribut de classe
2 chaque instance accède à cet attribut de classe

ce qui s’utilise par exemple comme :

if __name__=="__main__":
  q1 = Fraction(21,35)
  q2 = Fraction(9,45)
  print(q1,q2)                   # 3/5 1/5

  q1.switch_reduction()

  q3 = Fraction(21,35)
  q4 = Fraction(9,45)
  print(q3,q4)                   # 21/35 9/45

Cet attribut a une existence indépendante de toute instance. On peut donc le référencer même si aucune instance n’existe :

if __name__=="__main__":
  print(Fraction.reduction)            # True

Cet attribut est atteignable aussi via n’importe quelle instance :

if __name__=="__main__":
  q1 = Fraction(1,2)
  q2 = Fraction(2,3)
  print(Fraction.reduction)      # True
  print(q1.reduction)            # True
  print(q2.reduction)            # True

  q1.switch_reduction()

  print(Fraction.reduction)      # False
  print(q1.reduction)            # False
  print(q2.reduction)            # False

C’est embêtant de référencer la classe Fraction dans une méthode de la classe Fraction :

def switch_reduction(self):
  Fraction.reduction = not Fraction.reduction

car renommer la classe Fraction nécessiterait de modifier le code de cette méthode. L’attribut __class__ ayant pour valeur la classe courante, on peut écrire avantageusement :

def switch_reduction(self):
  self.__class__.reduction = not self.__class__.reduction

2. Méthode de classe

C’est une méthode définie au niveau de la classe. Elle a donc accès aux attributs de la classe, mais pas à ceux des instances (elle ne possède pas le paramètre self). Son périmètre d’action est donc restreint à la classe : elle ne peut appeler que des méthodes de classe.

Dans l’exemple précédent, la méthode switch_reduction() n’a pas pour objectif d’interagir avec les instances de Fraction, c’est donc typiquement une méthode de classe :

class Fraction:
  reduction = True (1)

  def __init__(self,n,d):
    self._num = n
    self._den = d
    if Fraction.reduction: (2)
      Fraction.reduire()

  @classmethod (1)
  def switch_reduction(cls):
    cls.reduction = not cls.reduction (2)
1 ceci indique que la méthode qui suit est une méthode de classe

Conséquence : le premier paramètre d’une méthode de classe ne reçoit pas une instance, mais la classe de l’instance pour laquelle elle est appelée (c’est pourquoi il est nommé par convention cls) :

Une méthode de classe a une existence indépendante de toute instance. Elle peut donc être appelée sans qu’aucune instance existe :

if __name__=="__main__":
  print(Fraction.reduction)            # True
  Fraction.switch_reduction()
  print(Fraction.reduction)            # False