// Python

Les classes en Python : la POO expliquée par l'exemple

Une classe Python est un modèle qui décrit un type d’objet : ses données (attributs) et ses comportements (méthodes). C’est le cœur de la programmation orientée objet (POO), un paradigme qui aide à organiser un programme autour d’entités cohérentes. Ce guide explique par l’exemple tout ce qu’il faut savoir : class, __init__, attributs et méthodes, self, héritage, encapsulation, méthodes spéciales et @property.

Définir une classe avec class

On déclare une classe avec le mot-clé class et une majuscule en tête du nom (convention PascalCase).

class Chien:
    pass

rex = Chien()   # on crée une instance
print(type(rex))   # <class '__main__.Chien'>

Une classe est un plan ; une instance est un objet concret construit à partir de ce plan. On peut créer autant d’instances qu’on veut.

Le constructeur init

La méthode spéciale __init__ s’exécute automatiquement à la création de l’objet. Elle sert à initialiser les attributs.

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

rex = Chien("Rex", 3)
print(rex.nom)   # Rex
print(rex.age)   # 3

Les valeurs passées à Chien("Rex", 3) sont reçues par __init__ et stockées dans l’objet.

Le rôle de self

self représente l’instance courante. C’est toujours le premier paramètre des méthodes, et Python le transmet automatiquement lors de l’appel. Grâce à lui, chaque objet garde ses propres données.

a = Chien("Rex", 3)
b = Chien("Bella", 5)
print(a.nom, b.nom)   # Rex Bella (chaque instance a son nom)

Vous n’écrivez jamais self à l’appel : a.nom suffit, Python passe a comme self en coulisses.

Attributs et méthodes

Un attribut est une donnée liée à l’objet ; une méthode est une fonction définie dans la classe.

class Chien:
    espece = "Canis familiaris"   # attribut de classe (partagé)

    def __init__(self, nom, age):
        self.nom = nom            # attribut d'instance
        self.age = age

    def aboyer(self):             # méthode
        return f"{self.nom} fait Ouaf !"

    def vieillir(self):
        self.age += 1

rex = Chien("Rex", 3)
print(rex.aboyer())   # Rex fait Ouaf !
rex.vieillir()
print(rex.age)        # 4

L’attribut de classe espece est commun à toutes les instances, tandis que nom et age sont propres à chacune. Les méthodes accèdent et modifient les attributs via self, un peu comme les fonctions Python manipulent leurs arguments.

L’héritage

L’héritage permet de créer une classe spécialisée à partir d’une classe existante, en réutilisant son code.

class Animal:
    def __init__(self, nom):
        self.nom = nom

    def parler(self):
        return "..."

class Chat(Animal):            # Chat hérite d'Animal
    def parler(self):          # on redéfinit (override) la méthode
        return "Miaou"

class Chien(Animal):
    def parler(self):
        return "Ouaf"

for animal in [Chat("Felix"), Chien("Rex")]:
    print(animal.nom, ":", animal.parler())
# Felix : Miaou
# Rex : Ouaf

La classe fille hérite des attributs et méthodes de la classe mère, et peut les redéfinir. Ce mécanisme, combiné au fait qu’un même appel (parler) produit un comportement différent selon le type, illustre le polymorphisme.

Appeler la classe mère avec super()

Pour étendre — plutôt que remplacer — le comportement du parent, on utilise super().

class Animal:
    def __init__(self, nom):
        self.nom = nom

class Chien(Animal):
    def __init__(self, nom, race):
        super().__init__(nom)   # appelle Animal.__init__
        self.race = race

rex = Chien("Rex", "Berger")
print(rex.nom, rex.race)   # Rex Berger

super().__init__(nom) réutilise l’initialisation du parent, ce qui évite de dupliquer le code.

L’encapsulation

L’encapsulation consiste à protéger l’accès direct aux données. En Python, la convention est le préfixe underscore.

class CompteBancaire:
    def __init__(self, solde):
        self._solde = solde         # "protégé" par convention

    def deposer(self, montant):
        if montant > 0:
            self._solde += montant

    def get_solde(self):
        return self._solde

compte = CompteBancaire(100)
compte.deposer(50)
print(compte.get_solde())   # 150

Un seul underscore (_solde) signale « usage interne, ne pas toucher directement ». Un double underscore (__solde) déclenche le name mangling, qui rend l’attribut plus difficile d’accès depuis l’extérieur.

Les méthodes spéciales (dunder)

Les méthodes entourées de doubles underscores (« dunder ») personnalisent le comportement des objets. __str__ définit l’affichage lisible.

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"Point({self.x}, {self.y})"

    def __eq__(self, autre):
        return self.x == autre.x and self.y == autre.y

    def __add__(self, autre):
        return Point(self.x + autre.x, self.y + autre.y)

p = Point(1, 2)
print(p)                 # Point(1, 2)  (grâce à __str__)
print(Point(1, 2) == p)  # True         (grâce à __eq__)
print(p + Point(3, 4))   # Point(4, 6)  (grâce à __add__)

Il en existe beaucoup : __len__, __repr__, __getitem__… Elles permettent à vos objets de se comporter comme des types natifs.

Le décorateur @property

@property transforme une méthode en attribut « calculé », accessible sans parenthèses, tout en gardant le contrôle.

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

    @property
    def aire(self):
        return self.largeur * self.hauteur

r = Rectangle(4, 5)
print(r.aire)   # 20 (sans parenthèses !)

On peut aussi contrôler l’écriture avec un setter et valider les valeurs.

class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

    @property
    def fahrenheit(self):
        return self._celsius * 9 / 5 + 32

    @fahrenheit.setter
    def fahrenheit(self, valeur):
        self._celsius = (valeur - 32) * 5 / 9

t = Temperature(25)
print(t.fahrenheit)   # 77.0
t.fahrenheit = 212
print(t._celsius)     # 100.0

@property est idéal pour exposer une valeur dérivée ou pour ajouter une validation sans changer l’interface de la classe.

Conclusion

Vous avez fait le tour de la POO en Python : définir une class, initialiser avec __init__, comprendre self, manipuler attributs et méthodes, réutiliser du code par héritage et super(), protéger les données avec l’encapsulation, personnaliser le comportement avec les méthodes spéciales et exposer des valeurs calculées via @property. Le meilleur moyen de progresser reste de modéliser vos propres objets (un panier, un joueur, une facture).

À lire ensuite