Programmation Orientée Objet en Python

@joekakone

Joseph Konka @joekakone

03/03/2026 · 12 min

Découvrez le paradigme orienté objet en Python : classes, instances, constructeur __init__, rôle fondamental de self, attributs d'instance et de classe, méthodes spéciales (__str__, __repr__) et décorateur @property.

Programmation Orientée Objet (POO) en Python#

Jusqu'à présent, nous avons principalement adopté une approche procédurale : notre code était composé de fonctions indépendantes recevant des données brutes en paramètres.

La Programmation Orientée Objet (POO) propose un changement de perspective fondamental : elle regroupe au sein d'une même entité logique appelée Objet les données qui le caractérisent (ses attributs) et les actions qu'il peut accomplir (ses méthodes).

En Python, la POO n'est pas une surcouche optionnelle : absolument tout en Python est un objet (les nombres, les chaînes, les listes, et même les fonctions !).


1. Classes vs Objets : Le moule et la matière#

Pour comprendre l'articulation entre une classe et un objet :

flowchart TD
    A["Classe CompteBancaire : Le Plan d'Architecte (Moule)"] -->|Instanciation 1| B["Objet compte_alice : (Solde = 2500 EUR)"]
    A -->|Instanciation 2| C["Objet compte_bob : (Solde = 1400 EUR)"]
  • La Classe (class) : c'est le plan d'architecte, le modèle abstrait qui définit les caractéristiques communes de tous les futurs objets.
  • L'Objet (ou Instance) : c'est l'entité concrète matérialisée en mémoire vive, créée à partir du modèle de la classe.

2. Définition d'une classe et le constructeur __init__#

On déclare une classe avec le mot-clé class (en convention PascalCase), et on initialise ses attributs d'instance dans la méthode spéciale __init__ :

class CompteBancaire:
    """Modèle représentant un compte bancaire client."""

    def __init__(self, titulaire: str, solde_initial: float = 0.0):
        # Attributs d'instance (spécifiques à chaque compte)
        self.titulaire = titulaire
        self.solde = solde_initial

    # Méthode d'instance
    def deposer(self, montant: float) -> None:
        if montant > 0:
            self.solde += montant
            print(f"Depot de {montant:.2f} EUR effectue. Nouveau solde : {self.solde:.2f} EUR")
        else:
            print("Erreur : Le montant du depot doit etre positif.")

    def retirer(self, montant: float) -> bool:
        if 0 < montant <= self.solde:
            self.solde -= montant
            print(f"Retrait de {montant:.2f} EUR effectue. Nouveau solde : {self.solde:.2f} EUR")
            return True
        else:
            print("Erreur : Provision insuffisante pour ce retrait.")
            return False

# Instanciation de deux comptes indépendants
compte_alice = CompteBancaire("Alice Martin", 1500.0)
compte_bob = CompteBancaire("Bob Diop")

compte_alice.deposer(500.0)   # Solde passe à 2000.0 EUR
compte_alice.retirer(2500.0)  # Échec provision

Le rôle fondamental du paramètre self#

[!IMPORTANT] En Python, le premier paramètre de toute méthode d'instance est obligatoirement self.
self représente une référence explicite vers l'instance précise sur laquelle la méthode est invoquée.
Lorsque vous écrivez compte_alice.deposer(500.0), Python traduit automatiquement cet appel en CompteBancaire.deposer(compte_alice, 500.0).


3. Les méthodes magiques indispensables : __str__ et __repr__#

Par défaut, afficher un objet avec print(compte_alice) affiche une adresse mémoire brute peu lisible comme <__main__.CompteBancaire object at 0x7f8a12>.

Pour offrir un rendu élégant, on surcharge deux méthodes dunder (double underscore) : - __str__(self) : représentation textuelle conviviale destinée à l'utilisateur final (print, str()). - __repr__(self) : représentation technique non ambiguë destinée au développeur et au débogage.

class CompteBancaire:
    def __init__(self, titulaire: str, solde: float = 0.0):
        self.titulaire = titulaire
        self.solde = solde

    def __str__(self) -> str:
        return f"Compte de {self.titulaire} (Solde : {self.solde:.2f} EUR)"

    def __repr__(self) -> str:
        return f"CompteBancaire(titulaire='{self.titulaire}', solde={self.solde})"

compte = CompteBancaire("Alice", 2500.0)
print(compte)        # Déclenche __str__ : Compte de Alice (Solde : 2500.00 EUR)
print(repr(compte))  # Déclenche __repr__: CompteBancaire(titulaire='Alice', solde=2500.0)

4. Attributs d'instance vs Attributs de classe#

  • Attribut d'instance : rattaché à une instance individuelle via self.x. Chaque objet a sa propre copie.
  • Attribut de classe : défini directement dans le corps de la classe. Il est partagé par toutes les instances créées !
class Etudiant:
    # Attribut de classe partagé par tous les étudiants
    etablissement = "AlgoJungle Academy"
    compteur_inscrits = 0

    def __init__(self, nom: str):
        # Attribut d'instance propre à cet étudiant
        self.nom = nom
        Etudiant.compteur_inscrits += 1

e1 = Etudiant("Koffi")
e2 = Etudiant("Sarah")

print("Etablissement :", Etudiant.etablissement)
print("Nombre total d'inscrits :", Etudiant.compteur_inscrits)  # 2

5. Encapsulation et le décorateur @property#

Contrairement à d'autres langages qui disposent de mots-clés stricts private ou public, Python adopte la philosophie du consentement mutuel : « We are all consenting adults here ».

  • Un tiret bas préfixé _attribut signale une convention d'attribut interne protégé.
  • Deux tirets bas __attribut déclenchent le masquage de nom (Name Mangling).

Pour protéger un attribut tout en permettant d'y accéder comme une variable naturelle sans écrire de lourds getters/setters (get_prix(), set_prix()), on utilise le décorateur @property :

class Article:
    def __init__(self, designation: str, prix: float):
        self.designation = designation
        self.prix = prix  # Déclenche automatiquement le setter ci-dessous !

    @property
    def prix(self) -> float:
        """Getter : retourne le prix."""
        return self._prix

    @prix.setter
    def prix(self, nouvelle_valeur: float) -> None:
        """Setter : valide la cohérence des données avant modification."""
        if nouvelle_valeur < 0:
            raise ValueError("Le prix d'un article ne peut pas etre negatif !")
        self._prix = nouvelle_valeur

produit = Article("Clavier RGB", 49.90)
print(f"Prix : {produit.prix} EUR")

# Tentative de saisie incohérente :
try:
    produit.prix = -15.0  # Bloqué par le setter !
except ValueError as e:
    print("Alerte de securite :", e)

6. Défi d'application#

Développez une classe Rectangle prenant à l'initialisation une longueur et une largeur. Implémentez deux propriétés en lecture seule (@property) calculant dynamiquement l'aire et le perimetre, ainsi que la méthode spéciale __eq__ permettant de comparer si deux rectangles ont des aires identiques avec l'opérateur ==.

Commentaires

Inscrivez-vous à une formation complète

Voir le catalogue