Introduction
Un étudiant à qui j'ai répondu avait des interrogations sur property, setter, attribut avec un simple/double underscore...
J'ai tenté de lui faire une réponse le plus claire possible, sans trop être dans le détail.
Une histoire de convention
Regardons ce code :
class Compte:
def __init__(self, nom, numero, balance):
self.nom = nom
self._numero = numero
self.balance = balance
@property
def numero(self):
return self._numero
@numero.setter
def numero(self, numero):
raise AttributeError("On ne change pas le numéro de compte !")
john = Compte(nom="John Smith", numero="123456", balance=20000)
L'underscore permet de signaler aux autres développeurs qu'il ne faut pas accéder directement à l'attribut depuis l'extérieur de la classe. On appelle ça rendre l'attribut privé.
Mais en réalité celui qui ne veut pas respecter cette convention peut y accéder quand même, car c'est juste une convention.
En effet, il est tout à fait possible d'afficher l'attribut privé :
print(john._numero)
Le double underscore c'est autre chose : on appelle ça le name mangling. Python rend l'accès à l'attribut plus difficile en gros. Mais en réalité, on peut y accéder quand même.
Si j'essaye de passer "normalement" par mon instance, Python lèvera un AttributeError :
print(john.__numero)
L'attribut est donc plus "difficile" d'accès, il faudrait en fait faire :
print(john._Compte__numero)
En résumé : que ce soit _ ou __, cela indique qu'il ne faut pas accéder à l'attribut en dehors de la classe.
Ce qui m'amène à dire qu'il ne faut pas modifier directement l'attribut, et donc passer par ce que l'on appelle un setter :
@numero.setter
def numero(self, numero):
raise AttributeError("On ne change pas de numéro de compte")
Property et Setter
Ce qui est intéressant avec un setter c'est qu'on peut mettre de multiples conditions. Avec un autre exemple :
class Personne:
def __init__(self, nom, age):
self.nom = nom
self._age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("L'âge ne peut pas être négatif")
else:
self._age = value
Il faut procéder ainsi pour passer par le setter :
# Création de l'instance
personne = Personne("Patrick", 30)
# Modification avec le setter
personne.age = -5
# Lève ValueError: L'âge ne peut pas être négatif
Le décorateur @property nous permet d'accéder à notre attribut privé _age comme s'il s'agissait d'un attribut normal, tout en gardant le contrôle sur sa lecture et sa modification.
Donc dans notre cas :
p = Personne("Alice", 25)
print(p.age) # Utilise le getter
p.age = 30 # Utilise le setter
p.age = -5 # Lève une ValueError
Il est important de noter que pour créer un setter avec @property, la méthode doit porter le même nom que la property suivie de .setter. Par exemple, si notre property s'appelle age, notre méthode de modification s'appellera obligatoirement @age.setter. Cette convention de nommage est obligatoire pour que Python fasse le lien entre les deux méthodes.
Nous avons donc vu plusieurs façons de contrôler l'accès à nos attributs en Python :
- Le simple underscore comme simple convention,
- Le double underscore pour complexifier l'accès,
- Les properties qui offrent une solution claire et élégante.
C'est un premier pas vers ce qu'on appelle l'encapsulation.
Tags
Retour