Python · Programmation

POO Python
Orientée Objet

Maîtrisez les 4 piliers de la POO — encapsulation, héritage, polymorphisme et abstraction — avec des exemples concrets et du code prêt à l'emploi.

📦 Encapsulation 🧬 Héritage 🔀 Polymorphisme 🎭 Abstraction

Introduction à la POO

🧩

La Programmation Orientée Objet organise le code autour d'objets qui regroupent des données (attributs) et des comportements (méthodes). C'est le paradigme dominant en développement logiciel moderne.

🏠
Analogie : la classe = le plan d'architecte. L'objet = la maison construite. On peut construire autant de maisons que l'on veut à partir du même plan, chacune avec ses propres caractéristiques.
Python — premier objet
class Chien:
    """Représente un chien."""
    pass

# Créer des objets (instances)
rex    = Chien()
lassie = Chien()

print(type(rex))
# <class '__main__.Chien'>

print(rex is lassie)          # False
print(isinstance(rex, Chien)) # True

Classes & Objets

ConceptRôleExemple
classDéfinir le mouleclass Chien:
InstanceObjet créé depuis la classerex = Chien()
selfRéférence à l'objet courantself.nom
__init__Constructeur automatiquedef __init__(self):
isinstanceVérifier le typeisinstance(rex, Chien)
Python
class Voiture:
    """Classe représentant une voiture."""

    def __init__(self, marque, annee):
        self.marque = marque
        self.annee  = annee

    def presenter(self):
        return f"{self.marque} ({self.annee})"


v1 = Voiture("Renault", 2020)
v2 = Voiture("Peugeot", 2022)

print(v1.presenter())  # Renault (2020)
v1.marque = "Citroën"
print(v2.marque)        # Peugeot — inchangé

Le Constructeur __init__

La méthode __init__ est appelée automatiquement à la création de chaque objet. Elle initialise les attributs d'instance.

💡

Le paramètre self représente l'objet lui-même. Il doit toujours être le premier paramètre des méthodes d'instance — Python le passe automatiquement.

🔑

On peut donner des valeurs par défaut aux paramètres de __init__ pour rendre certains attributs optionnels.

Python
class Chien:
    def __init__(self, nom, race, age=0):
        self.nom  = nom
        self.race = race
        self.age  = age    # valeur par défaut

rex   = Chien("Rex",   "Berger", 3)
milou = Chien("Milou", "Terrier") # age=0

print(rex.nom)    # Rex
print(rex.age)    # 3
print(milou.age)  # 0

Attributs

Les attributs d'instance sont propres à chaque objet. Ils sont définis via self dans __init__. Chaque instance a sa propre copie indépendante.

Instance
class Voiture:
    def __init__(self, marque, annee):
        self.marque = marque  # instance
        self.annee  = annee

v1 = Voiture("Renault", 2020)
v2 = Voiture("Peugeot", 2022)
v1.marque = "Citroën"
print(v2.marque) # Peugeot (inchangé)

Les attributs de classe sont partagés entre toutes les instances. Définis directement dans le corps de la classe. Utiles pour des constantes ou des compteurs globaux.

Classe
class Voiture:
    nb_voitures = 0    # attribut de classe
    VITESSE_MAX = 250  # constante

    def __init__(self, marque):
        self.marque = marque
        Voiture.nb_voitures += 1

v1 = Voiture("Renault")
v2 = Voiture("Peugeot")
print(Voiture.nb_voitures)  # 2

Méthodes

Instance
def methode(self, ...)

Accède et modifie les attributs de l'instance via self. La méthode la plus courante.

def aboyer(self):
    print(f"{self.nom} aboie !")

rex.aboyer()  # Rex aboie !
@classmethod
def methode(cls, ...)

Reçoit la classe (cls) plutôt que l'instance. Utile pour des constructeurs alternatifs.

@classmethod
def depuis_dict(cls, d):
    return cls(d["nom"], d["age"])
@staticmethod
def methode(...)

Ni self ni cls. Fonction utilitaire liée logiquement à la classe.

@staticmethod
def est_adulte(age):
    return age >= 18

Humain.est_adulte(20) # True

Héritage

L'héritage permet à une classe enfant de réutiliser le code d'une classe parent. L'enfant hérite de tous les attributs et méthodes du parent.

🔑

super() appelle la méthode du parent. Indispensable dans __init__ pour initialiser correctement la classe parente avant d'ajouter les attributs enfant.

Animal
nom, age  ·  manger()
Chien
race · aboyer()
Chat
couleur · ronron()
Python — héritage + super()
class Animal:
    def __init__(self, nom, age):
        self.nom = nom ; self.age = age

    def manger(self):
        print(f"{self.nom} mange.")


class Chien(Animal):     # hérite de Animal
    def __init__(self, nom, age, race):
        super().__init__(nom, age)
        self.race = race

    def aboyer(self):
        print("Woof !")


rex = Chien("Rex", 3, "Berger")
rex.manger()                        # hérité
rex.aboyer()                        # propre à Chien
print(isinstance(rex, Animal))     # True
print(issubclass(Chien, Animal))  # True

Polymorphisme

Le polymorphisme permet à des objets de types différents de répondre au même appel de méthode, chacun à sa façon. En Python, il est naturel grâce au duck typing.

🦆
Duck typing : "Si ça ressemble à un canard et ça cancane, c'est un canard." Python ne vérifie pas le type — il vérifie si la méthode existe.
💡

Pour forcer l'implémentation dans les sous-classes, utiliser from abc import ABC, abstractmethod.

Python — polymorphisme + ABC
from abc import ABC, abstractmethod

class Animal(ABC):
    @abstractmethod
    def parler(self):
        pass    # Sous-classes DOIVENT implémenter

class Chien(Animal):
    def parler(self): print("Woof !")

class Chat(Animal):
    def parler(self): print("Miaou !")

class Vache(Animal):
    def parler(self): print("Meuh !")


for a in [Chien(), Chat(), Vache()]:
    a.parler()   # Woof ! / Miaou ! / Meuh !

Encapsulation

L'encapsulation consiste à protéger les données internes et à contrôler leur accès via des méthodes dédiées.

nomPublic — accessible partout
_nomProtégé — convention : usage interne
__nomPrivé — name mangling Python
⚠️

Python ne bloque pas réellement l'accès — ce sont des conventions. __nom devient _Classe__nom (name mangling) mais reste accessible.

Python — @property
class Temperature:
    def __init__(self, celsius):
        self._celsius = celsius

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

    @celsius.setter
    def celsius(self, valeur):
        if valeur < -273.15:
            raise ValueError("Impossible")
        self._celsius = valeur        # setter validé

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


t = Temperature(100)
print(t.fahrenheit)   # 212.0
t.celsius = 37         # utilise le setter
Compte bancaire — attribut privé __solde
class CompteBancaire:
    def __init__(self, solde):
        self.__solde = solde   # privé — name mangling → _CompteBancaire__solde

    @property
    def solde(self): return self.__solde  # lecture seule

    def deposer(self, montant):
        if montant > 0: self.__solde += montant
        else: print("Montant invalide")

    def retirer(self, montant):
        if 0 < montant <= self.__solde: self.__solde -= montant
        else: print("Fonds insuffisants")

compte = CompteBancaire(1000)
compte.deposer(500)
print(compte.solde)       # 1500
# compte.__solde          → AttributeError !

Méthodes Magiques (Dunder)

Les méthodes entourées de doubles underscores (dunders) définissent le comportement des opérateurs et fonctions Python natifs sur vos objets.

__str__
Représentation lisible (print(), str())
__repr__
Représentation officielle pour les développeurs
__len__
Résultat de len(objet)
__eq__
Comparaison avec ==
__lt__
Comparaison avec <
__add__
Opérateur +
__getitem__
Accès par index obj[i]
__contains__
Opérateur in
__iter__
Rendre l'objet itérable (for x in obj)
__enter__ / __exit__
Context manager (with)
Exemple complet — classe Vecteur
class Vecteur:
    def __init__(self, x, y):
        self.x = x ; self.y = y

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

    def __repr__(self):
        return f"Vecteur(x={self.x!r}, y={self.y!r})"

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

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

    def __len__(self):
        return int((self.x**2 + self.y**2)**0.5)

v1 = Vecteur(3, 4) ; v2 = Vecteur(1, 2)
print(v1)          # Vecteur(3, 4)
print(v1 + v2)     # Vecteur(4, 6)
print(v1 == v2)    # False
print(len(v1))     # 5

Cheat Sheet POO Python

🏗️ Structure de base

class Cls(Parent):Héritage
def __init__(self):Constructeur
super().__init__()Appeler le parent
self.attr = valAttribut d'instance
Cls.attr = valAttribut de classe

🔧 Types de méthodes

def m(self)Méthode d'instance
@classmethodReçoit cls
@staticmethodNi self ni cls
@propertyGetter Pythonique
@prop.setterSetter avec validation

🔍 Fonctions utiles

isinstance(o, C)Vérifier le type
issubclass(C, P)Vérifier l'héritage
hasattr(o, 'a')Attribut existe ?
getattr(o, 'a')Lire dynamiquement
dir(o)Lister les membres

📋 Dunders essentiels

__init__Constructeur
__str__print(obj)
__repr__repr(obj)
__eq__obj == autre
__len__len(obj)
__add__obj + autre
🐍

La POO en Python est flexible : Python ne force pas l'encapsulation stricte, mais les conventions (_, __) permettent de signaler l'intention. Combine POO et fonctions selon les besoins — Python n'impose pas un paradigme unique.