logo oujood
🔍

Les propriétés Python avec @property

@property permet de contrôler l'accès aux attributs d'une classe sans changer l'interface. Ce cours explique getters, setters et deleters avec des exemples pratiques.

OUJOOD.COM

Dans beaucoup de langages orientés objet, les attributs d'une classe sont systématiquement cachés derrière des méthodes get_valeur() et set_valeur(). En Python, cette convention n'est pas la norme. On commence par des attributs publics simples, et on ajoute du contrôle uniquement quand c'est nécessaire — sans casser le code existant.

C'est précisément ce que permet @property : transformer un attribut en propriété contrôlée, avec de la validation ou du calcul à l'accès ou à l'assignation, tout en conservant la syntaxe objet.attribut. Le code appelant ne voit aucune différence.

Le problème : accès direct sans contrôle

Sans propriété, rien n'empêche d'assigner n'importe quelle valeur à un attribut, y compris des valeurs absurdes.

  📋 Copier le code

class Personne:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age

p = Personne("Alice", 30)
p.age = -5
# Python accepte sans broncher — problème !
print(p.age)
# Résultat : -5

La solution : @property pour un getter

@property transforme une méthode en attribut en lecture. On accède à la valeur avec objet.age comme si c'était un attribut normal, mais Python exécute en réalité la méthode.

  📋 Copier le code

class Personne:
    def __init__(self, nom, age):
        self.nom = nom
        self._age = age  # Convention : _ signale un attribut "interne"

    @property
    def age(self):
        return self._age

p = Personne("Alice", 30)
print(p.age)
# Résultat : 30 — accès comme un attribut normal

# Tentative de modification → erreur car pas de setter défini
# p.age = 31  → AttributeError: can't set attribute

Ajouter un setter avec @property.setter

Pour autoriser la modification tout en validant la valeur, ajoutez un setter avec le décorateur @nom_propriete.setter.

  📋 Copier le code

class Personne:
    def __init__(self, nom, age):
        self.nom = nom
        self.age = age  # Appelle directement le setter

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, valeur):
        if not isinstance(valeur, int) or valeur < 0 or valeur > 150:
            raise ValueError(f"Âge invalide : {valeur}")
        self._age = valeur

p = Personne("Alice", 30)
print(p.age)
# Résultat : 30

p.age = 31
print(p.age)
# Résultat : 31

try:
    p.age = -5
except ValueError as e:
    print(e)
# Résultat : Âge invalide : -5

Remarquez que dans __init__, on écrit self.age = age (sans underscore) — cela passe par le setter et déclenche la validation dès la création de l'objet.

Ajouter un deleter avec @property.deleter

Le deleter est appelé quand on utilise del objet.attribut. C'est moins fréquent, mais utile pour nettoyer des ressources ou remettre un attribut à son état initial.

  📋 Copier le code

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, valeur):
        if valeur < 0:
            raise ValueError("L'âge ne peut pas être négatif.")
        self._age = valeur

    @age.deleter
    def age(self):
        print("Suppression de l'âge.")
        del self._age

p = Personne("Alice", 30)
del p.age
# Résultat : Suppression de l'âge.

Propriété calculée (read-only)

Une propriété n'a pas forcément besoin de stocker une valeur — elle peut la calculer à la volée depuis d'autres attributs. Dans ce cas, on définit uniquement le getter, sans setter.

  📋 Copier le code

class Rectangle:
    def __init__(self, largeur, hauteur):
        self.largeur = largeur
        self.hauteur = hauteur

    @property
    def aire(self):
        # Calculée à chaque accès, aucun stockage
        return self.largeur * self.hauteur

    @property
    def perimetre(self):
        return 2 * (self.largeur + self.hauteur)

r = Rectangle(5, 3)
print(r.aire)
# Résultat : 15

print(r.perimetre)
# Résultat : 16

r.largeur = 10
print(r.aire)
# Résultat : 30 — recalculée automatiquement

Exemple complet : classe Température

Un exemple plus concret : une classe qui stocke une température en Celsius, avec une propriété calculée pour Fahrenheit et une validation à l'assignation.

  📋 Copier le code

class Temperature:
    ABSOLU_ZERO = -273.15

    def __init__(self, celsius):
        self.celsius = celsius  # Passe par le setter

    @property
    def celsius(self):
        return self._celsius

    @celsius.setter
    def celsius(self, valeur):
        if valeur < Temperature.ABSOLU_ZERO:
            raise ValueError(
                f"Température inférieure au zéro absolu ({Temperature.ABSOLU_ZERO}°C)."
            )
        self._celsius = valeur

    @property
    def fahrenheit(self):
        # Propriété calculée, lecture seule
        return self._celsius * 9/5 + 32

    def __str__(self):
        return f"{self._celsius}°C / {self.fahrenheit:.1f}°F"

t = Temperature(100)
print(t)
# Résultat : 100°C / 212.0°F

t.celsius = 0
print(t.fahrenheit)
# Résultat : 32.0

try:
    t.celsius = -300
except ValueError as e:
    print(e)
# Résultat : Température inférieure au zéro absolu (-273.15°C).

Quand utiliser @property ?

Quelques règles simples pour décider :

  • Commencez par des attributs publics simples. N'ajoutez pas de propriétés par habitude ou par précaution.
  • Ajoutez @property quand vous avez besoin de validation — empêcher les valeurs négatives, vérifier un format, lever une exception explicite.
  • Utilisez une propriété calculée quand une valeur se déduit toujours d'autres attributs et qu'il n'y a aucune raison de la stocker séparément.
  • Ne renommez pas vos attributs juste pour ajouter des underscores. L'underscore simple (_attribut) signale une convention "interne" — Python ne l'applique pas de force.

Par carabde | Mis à jour le 21 avril 2026