ProgrammationDéveloppeur Backend Python

Qu'est-ce que les décorateurs de propriétés de classe (@property) en Python, comment fonctionnent-ils et à quoi servent-ils ?

Réussissez les entretiens avec l'assistant IA Hintsage

Réponse.

Le décorateur @property permet de transformer une méthode en "attribut virtuel" de la classe : il apparaît pour l'utilisateur de la classe comme une propriété ordinaire, mais est géré par la logique d'une fonction Python. Au départ, tous les attributs d'instance en Python étaient accessibles directement, mais pour soutenir l'encapsulation, un mécanisme de contrôle d'accès aux données sans modifier l'interface de la classe était nécessaire.

Histoire :

Dans les premières versions de Python, il n'y avait pas de mécanisme explicite pour restreindre l'accès aux attributs. La tâche d'encapsulation était réalisée par le biais de conventions (par exemple, le soulignement), mais tout changement de logique de stockage/validation des valeurs compromettait la compatibilité. Avec l'apparition du décorateur @property, il est devenu possible de déclarer une méthode comme un attribut, de l'équiper d'un getter, d'un setter et d'un deleter, tout en conservant l'interface précédente de la classe.

Problème :

Lorsque l'on doit par la suite ajouter une logique de validation ou de calcul de la valeur d'un champ, reconstruire l'ensemble de l'interface est trop coûteux — il faut changer tous les endroits d'accès à la variable. Par ailleurs, la visibilité de l'implémentation interne du champ ne doit pas être révélée à l'utilisateur externe de la classe. @property permet de "virtualiser" facilement l'accès.

Solution :

@property met en œuvre le modèle "getter/setter avec protection de l'interface" : on peut ajouter une propriété calculée ou contrôler l'établissement de la valeur sans modifier le code client.

Exemple de code :

class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("La température est inférieure au zéro absolu!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # Utilisation obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError

Caractéristiques clés :

  • Permet de réaliser des propriétés calculées, validées ou mises en cache sans modifier l'interface client
  • Fournit une interface de propriété (sans parenthèses lors de l'accès)
  • Permet de contrôler à la fois la lecture, l'écriture et la suppression de la valeur

Questions pièges.

Peut-on toujours ajouter une property à n'importe quel champ de classe sans compromettre la rétrocompatibilité ?

Non. Si l'attribut était public et utilisé comme une variable ordinaire, le remplacement par une property peut se faire de manière transparente. Mais si quelque part les accès se faisaient par __dict__ ou que des vérifications de type étaient effectuées via type(obj.attr), cela pourrait entraîner un comportement inattendu.

Peut-on déclarer seulement un setter sans getter pour une propriété ?

Non, un getter (méthode avec @property) est d'abord requis, sinon Python ne sait pas à quelle propriété "lier" le setter.

Quel est l'ordre des décorateurs @property/@setter/@deleter et pourquoi ?

On écrit toujours @property en premier (il crée l'objet propriété), puis via le nom de la méthode — @<nom>.setter et @<nom>.deleter (ils complètent la propriété précédemment créée).

class A: @property def value(self): ... @value.setter def value(self, v): ...

Erreurs typiques et anti-modèles

  • Accéder directement à l'attribut privé (par exemple, self._celsius) en dehors de la classe
  • Violation du principe d'encapsulation : logique superflue dans le getter/setter
  • Redéfinir une property dans les héritiers avec conflit de noms

Exemple de la vie réelle

Cas négatif

Les champs de classe étaient publics, puis une propriété calculée a été ajoutée via une property après la publication de l'API publique. Dans l'ancien code, l'accès à l'attribut modifie implicitement son comportement ou provoque une erreur.

Avantages :

  • L'utilisateur peut ne pas changer ses appels à l'attribut

Inconvénients :

  • Des échecs inattendus apparaissent dans le code si la logique d'accès devient celle du setter/getter
  • Compatibilité faible si le client utilisait un accès direct à dict

Cas positif

Dès la conception, les champs ont été définis comme privés (avec un seul soulignement) et les utilisateurs n'ont travaillé qu'à travers des méthodes/propriétés. L'ajout d'une nouvelle logique dans la property à l'avenir s'est fait sans modifications de l'interface client.

Avantages :

  • Encapsulation
  • Possibilité d'ajouter facilement de la validation et des calculs

Inconvénients :

  • Nécessite une discipline de documentation et de respect des conventions ; faible confidentialité (un seul soulignement n'est pas suffisant pour une protection totale)