class Pupil:
def __init__(self, grade):
self.grade = grade
p1 = Pupil(50)
p1.grade = 85 # Accès direct à l'attribut
print(p1.grade) # Résultat : 85
p2 = Pupil(120)
print(p2.grade) # Résultat : 120, une note ne devrait pas dépasser 100
Pour restreindre l'accès direct, on rend l'attribut privé en le préfixant d'un double underscore (__). Cependant, cela empêche également sa lecture depuis l'extérieur de la classe.
class Pupil:
def __init__(self, grade):
self.__grade = grade
p1 = Pupil(50)
# print(p1.__grade) # Provoque une AttributeError
La solution classique consiste à implémenter des méthodes pour lire et moidfier cette variable privée. On parle alors d'encapsulation.
class Pupil:
def __init__(self, grade):
self.__grade = grade
def define_grade(self, g):
self.__grade = g
def retrieve_grade(self):
return self.__grade
p1 = Pupil(50)
print(p1.retrieve_grade()) # Résultat : 50
p1.define_grade(85)
print(p1.retrieve_grade()) # Résultat : 85
Bien que cela contrôle l'accès, aucune validation n'est encore appliquée. Ajoutons donc une vérification dans la méthode de définition.
class Pupil:
def __init__(self, grade):
self.__grade = grade
def define_grade(self, g):
if 0 <= int(g) <= 100:
self.__grade = g
else:
raise ValueError("La note doit être comprise entre 0 et 100 !")
def retrieve_grade(self):
return self.__grade
p1 = Pupil(50)
p1.define_grade(85)
print(p1.retrieve_grade()) # Résultat : 85
# p1.define_grade(150) # Lève une exception
L'inconvénient ici est que l'on doit appeler des méthodes pour interagir avec l'attribut, ce qui est moins intuitif que l'accès par point (.). C'est ici qu'intervient le décorateur @property.
class Pupil:
def __init__(self, grade):
self.__grade = grade
@property
def grade(self):
return self.__grade
@grade.setter
def grade(self, g):
if 0 <= int(g) <= 100:
self.__grade = g
else:
raise ValueError("La note doit être comprise entre 0 et 100 !")
p1 = Pupil(50)
print(p1.grade) # Résultat : 50, accès comme un attribut classique
p1.grade = 85 # Modification comme un attribut classique
print(p1.grade) # Résultat : 85
Enfin, il est crucial de bien instancier les objets. Si l'on écrit p1 = p2 = Pupil(50), les deux variables pointeront vers le même objet en mémoire, et modifier l'un affectera l'autre. Pour éviter cela, il faut instancier chaque objet séparément.
class Pupil:
def __init__(self, g):
self.__grade = g
@property
def grade(self):
return self.__grade
@grade.setter
def grade(self, g):
if 0 <= int(g) <= 100:
self.__grade = g
else:
raise ValueError("Valeur invalide !")
p1 = Pupil(50)
p2 = Pupil(63)
p1.grade = 89
print(p1.grade) # Résultat : 89
print(p2.grade) # Résultat : 63, l'objet p2 reste indépendant