Question 1Quelle est l'affirmation la plus précise sur les variables privées de Python ?
Variables privées et encapsulation — Accès sûr via getter / setter
Apprends les variables privées et l'encapsulation en Python. La convention _x vs le name mangling __x, get_xxx / set_xxx pour un accès sûr, et le style Pythonique @property / @xxx.setter — tout en pratique.
La dernière fois on a couvert les deux premiers piliers de la POO — héritage et polymorphisme. Cet article boucle le troisième : l'encapsulation.
Variables privées — Python n'a pas de vraie confidentialité
Java et C++ ont un mot-clé private qui bloque l'accès depuis l'extérieur dès que tu le déclares. Python n'a pas de confidentialité imposée par le langage. À la place, le nombre de underscores en tête signale « c'est pour usage interne » ou « ne touche pas directement à ça » — une convention entre programmeurs, pas une règle stricte.
Underscore simple _x — confidentialité par convention seulement
Ajouter un seul _ devant un nom dit à la communauté Python « cet attribut est pour l'usage interne de la classe — n'y accède pas directement depuis l'extérieur ». Le paramètre de __init__ garde son nom simple ; seul le champ self. reçoit le _ en tête.
class UserAccount:
def __init__(self, owner_name, balance):
self._owner_name = owner_name # interne -> préfixer avec _
self._balance = balance
def get_info(self): # accesseur exposé
return {"owner": self._owner_name, "balance": self._balance}
user = UserAccount("Alice", 50000)
print(user._balance) # 50000 <- marche mais déconseillé
print(user.get_info()) # {'owner': 'Alice', 'balance': 50000} <- recommandé
Underscore double __x — name mangling
Ajouter deux _ en tête fait réécrire le nom de l'attribut lui-même par Python. Si tu écris self.__pin = 1234 dans une classe Account, le nom réellement stocké devient _Account__pin. C'est ce qu'on appelle le name mangling — obj.__pin depuis l'extérieur ne trouve rien, donc l'accès devient effectivement bien plus difficile.
obj.__pin directement échoue avec AttributeError parce que cette clé n'est pas là.class Account:
def __init__(self, owner, pin):
self._owner = owner # confidentialité par convention seulement
self.__pin = pin # name-mangled (devient _Account__pin)
acc = Account("Alice", 1234)
print(acc._owner) # Alice <- marche normalement
# print(acc.__pin) # AttributeError <- non visible directement
print(acc._Account__pin) # 1234 <- le nom mangled y accède
« __ » n'est pas un mur absolu non plus
Le double underscore empêche l'accès direct via obj.__pin, ce qui est un cran plus fort. Mais quiconque connaît le nom mangled obj._Account__pin peut toujours y accéder. Ce n'est pas une vraie confidentialité. Dans les vrais projets, l'underscore simple _x est bien plus courant sauf raison spécifique d'utiliser le mangling.
Encapsulation — restreindre l'accès à des méthodes dédiées
L'encapsulation est l'idée de design « regrouper les attributs de données et les méthodes qui agissent dessus dans une seule classe, et forcer le code extérieur à passer par un petit ensemble de portes d'entrée publiées ». Alors comment construit-on ces portes d'entrée ?
_price sans contrôle. Passer par une méthode signifie que le setter valide le type et la plage en un seul endroit.Le style le plus basique consiste à écrire les méthodes get_xxx / set_xxx à la main. Dans le setter, fais une vérification isinstance pour le type et un contrôle de plage, et raise ValueError(...) si quelque chose cloche. Avec ça en place, aucune valeur poubelle n'atteint jamais _price.
class Product:
def __init__(self, name, price, stock):
self._name = name
self._price = price
self._stock = stock
def get_price(self):
return self._price
def set_price(self, price):
if isinstance(price, int) and price >= 0:
self._price = price
else:
raise ValueError("price must be a non-negative integer")
product = Product("T-shirt", 1500, 30)
print(product.get_price()) # 1500
product.set_price(2000)
print(product.get_price()) # 2000
# product.set_price(-100) # ValueError
@property et @xxx.setter
Le style get_price() / set_price(...) est clair, mais les sites d'appel finissent par avoir l'air très méthode-call — pas le plus propre. L'idiome Python plus poli utilise deux décorateurs : @property et @xxx.setter.
Avec ceux-là, le site d'appel reste product.price / product.price = 2000 — accès d'attribut classique — mais en dessous, les méthodes getter et setter sont appelées. C'est une structure à deux couches où la syntaxe reste simple mais la logique tourne quand même.
@property redirige les lectures et @price.setter redirige les écritures vers des appels de méthode. La validation vit à l'intérieur du setter.class Product:
def __init__(self, name, price):
self._name = name
self._price = price
@property
def price(self): # getter
return self._price
@price.setter
def price(self, value): # setter — le nom doit correspondre au getter
if not isinstance(value, int) or value < 0:
raise ValueError("price must be a non-negative integer")
self._price = value
@property
def label(self): # propriété calculée — valeur dérivée
return f"{self._name} ({self._price})"
product = Product("T-shirt", 1500)
print(product.price) # 1500 <- @property est invoqué
product.price = 2000 # <- @price.setter est invoqué
print(product.price) # 2000
print(product.label) # T-shirt (2000) <- propriété calculée
Garde le nom du setter identique à celui du getter
Le price dans @price.setter doit correspondre au nom de méthode du @property def price précédent. Python interprète le décorateur comme « attache une version d'écriture au même objet price qui a déjà une version de lecture » — si le nom dérive, ils sont traités comme des choses séparées.
Les trois piliers de la POO
- Protection des données — sépare l'état interne de l'API publique via
_x - Cohérence — concentre la validation dans les setters, en un seul endroit
- Liberté d'implémentation — change les internes sans changer l'API publique
- Style Pythonique — convention plus
@property, pas une application du langage
- Réutiliser la machinerie d'un parent
- Même nom de méthode, comportement différent par type
- Canaliser l'accès extérieur par un petit ensemble de portes
Vérification des connaissances
Répondez à chaque question une par une.
Question 2Quel est le plus grand bénéfice d'utiliser @property et @xxx.setter ?
Question 3Dans class Account: tu as écrit self.__pin = 1234. Depuis l'extérieur, acc.__pin lève AttributeError. Quel nom est réellement stocké sur l'instance ?