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.
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.
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
| Concept | Rôle | Exemple |
|---|---|---|
class | Définir le moule | class Chien: |
| Instance | Objet créé depuis la classe | rex = Chien() |
self | Référence à l'objet courant | self.nom |
__init__ | Constructeur automatique | def __init__(self): |
isinstance | Vérifier le type | isinstance(rex, Chien) |
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.
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.
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.
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
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 !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"])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) # TrueHé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.
nom, age · manger()
race · aboyer()
couleur · ronron()
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.
Pour forcer l'implémentation dans les sous-classes, utiliser from abc import ABC, abstractmethod.
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 PythonPython ne bloque pas réellement l'accès — ce sont des conventions. __nom devient _Classe__nom (name mangling) mais reste accessible.
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
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.
print(), str())len(objet)==<+obj[i]infor x in obj)with)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 = val | Attribut d'instance |
Cls.attr = val | Attribut de classe |
🔧 Types de méthodes
def m(self) | Méthode d'instance |
@classmethod | Reçoit cls |
@staticmethod | Ni self ni cls |
@property | Getter Pythonique |
@prop.setter | Setter 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.