🐍
Cours Complet
Programmation Orientée
Objet
en Python
Pour débutantes • Explications simples • Exemples détaillés
Ce cours couvre :
✅ Procédural vs POO
✅ Classes, Objets, Attributs, Méthodes
✅ Encapsulation (public, protégé, privé, getters/setters, @property)
✅ Héritage simple, multiple, super()
✅ Polymorphisme (overriding vs overloading)
✅ Abstraction (module abc)
✅ __str__, __repr__, __dict__, __doc__
✅ Garbage Collector et comptage de références
1. Programmation Procédurale vs POO
Avant de plonger dans la POO, comprenons d'abord la différence avec ce que tu connais
probablement déjà : la programmation procédurale.
🔧 La programmation procédurale
Imagine que tu dois gérer une bibliothèque. En procédural, tu crées des variables séparées
et des fonctions indépendantes qui agissent sur ces données.
# === STYLE PROCÉDURAL ===
# On définit les données séparément
nom_livre = "Le Petit Prince"
auteur_livre = "Antoine de Saint-Exupéry"
pages_livre = 96
# Les fonctions sont séparées des données
def afficher_livre(nom, auteur, pages):
print(f"📖 {nom} par {auteur} - {pages} pages")
afficher_livre(nom_livre, auteur_livre, pages_livre)
# Problème : si on a 100 livres, on crée 300 variables !
La Programmation Orientée Objet (POO)
En POO, on regroupe les données ET les fonctions qui les concernent dans une seule
structure appelée CLASSE. C'est comme une boîte bien organisée !
💡 Analogie La POO c'est comme une fiche d'identité : toutes les informations sur une
personne (nom, âge, adresse) sont regroupées ensemble, avec les actions que cette
personne peut faire (marcher, parler, manger).
# === STYLE POO ===
class Livre:
def __init__(self, nom, auteur, pages):
[Link] = nom # le nom du livre
[Link] = auteur # son auteur
[Link] = pages # nombre de pages
def afficher(self):
print(f"📖 {[Link]} par {[Link]} - {[Link]} pages")
# Créer un livre, c'est simple !
mon_livre = Livre("Le Petit Prince", "Saint-Exupéry", 96)
mon_livre.afficher()
# Résultat : 📖 Le Petit Prince par Saint-Exupéry - 96 pages
Les 4 grands avantages de la POO :
• 📦 Organisation : données + actions regroupées logiquement
• ♻️Réutilisabilité : une classe peut créer des centaines d'objets
• 🔧 Maintenance : modifier une classe modifie tous ses objets
• 📖 Lisibilité : le code représente le monde réel
2. Classe, Objet, Attribut, Méthode
📐 La Classe — le moule
💡 Analogie Une classe c'est comme le moule d'un gâteau. Le moule lui-même n'est pas
un gâteau, mais il permet d'en créer autant qu'on veut, tous de la même forme !
La classe définit la STRUCTURE : quelles informations elle contient et quelles actions elle
peut faire.
class Chien: # Le mot 'class' crée une classe, nom en majuscule
"""Classe qui représente un chien.""" # doc de la classe
def __init__(self, nom, race, age): # constructeur
"""Constructeur : appelé à chaque création."""
[Link] = nom # attribut 'nom'
[Link] = race # attribut 'race'
[Link] = age # attribut 'age'
📦 L'Objet — le gâteau fait avec le moule
Un objet est une INSTANCE de classe. C'est une réalisation concrète de la classe. Tu peux
créer autant d'objets que tu veux depuis la même classe.
# On crée des OBJETS (instances) à partir de la classe
rex = Chien("Rex", "Berger", 3) # objet 1
milou = Chien("Milou", "Fox", 2) # objet 2
lassie = Chien("Lassie", "Colley", 5) # objet 3
# Chaque objet est INDÉPENDANT
print([Link]) # Rex
print([Link]) # Fox
Les Attributs — les caractéristiques
Les attributs sont les DONNÉES stockées dans l'objet. C'est comme les caractéristiques sur
une fiche d'identité.
class Chien:
# ATTRIBUT DE CLASSE (partagé par tous les chiens)
espece = "Canis lupus familiaris"
def __init__(self, nom, age):
# ATTRIBUTS D'INSTANCE (uniques à chaque chien)
[Link] = nom # propre à cet objet
[Link] = age # propre à cet objet
rex = Chien("Rex", 3)
print([Link]) # Canis lupus familiaris (attribut de classe)
print([Link]) # Rex (attribut d'instance)
⚙️Les Méthodes — les actions
Les méthodes sont des FONCTIONS définies à l'intérieur d'une classe. Elles définissent ce
que l'objet peut faire.
💡 Astuce Le premier paramètre d'une méthode est toujours 'self'. Il représente l'objet lui-
même. C'est grâce à lui que la méthode peut accéder aux attributs de l'objet.
class Chien:
def __init__(self, nom, age):
[Link] = nom
[Link] = age
def aboyer(self): # méthode simple
print(f"{[Link]} dit : Wouf !")
def anniversaire(self): # modifie un attribut
[Link] += 1 # augmente l'âge de 1
print(f"{[Link]} a maintenant {[Link]} ans !")
def se_presenter(self): # utilise plusieurs attributs
print(f"Je suis {[Link]}, j'ai {[Link]} ans.")
# Utilisation
rex = Chien("Rex", 3)
[Link]() # Rex dit : Wouf !
[Link]() # Rex a maintenant 4 ans !
rex.se_presenter() # Je suis Rex, j'ai 4 ans.
3. L'Encapsulation
L'encapsulation, c'est l'idée de PROTÉGER les données d'un objet. On contrôle qui peut lire
ou modifier les attributs.
💡 Analogie C'est comme un coffre-fort : certaines choses sont accessibles à tous,
d'autres nécessitent une clé, et d'autres encore sont totalement secrètes.
Attributs Public, Protégé et Privé
class CompteBancaire:
def __init__(self, titulaire, solde):
# PUBLIC : accessible partout, pas de préfixe
[Link] = titulaire # tout le monde peut lire
# PROTÉGÉ : un seul underscore '_'
self._banque = "Crédit Général" # convention : usage interne
# PRIVÉ : deux underscores '__'
self.__solde = solde # Python le cache vraiment
compte = CompteBancaire("Alice", 1500)
print([Link]) # ✅ OK : Alice
print(compte._banque) # ⚠️ OK mais déconseillé
print(compte.__solde) # ❌ ERREUR ! AttributeError
Type Syntaxe Accessible depuis Usage
Public nom Partout Données normales
Protégé _nom Classe + sous- Usage interne
classes
Privé __nom Classe uniquement Données sensibles
🔑 Getters et Setters
Les getters et setters sont des méthodes pour LIRE (get) et MODIFIER (set) un attribut privé
de façon contrôlée.
class CompteBancaire:
def __init__(self, solde):
self.__solde = solde # attribut privé
def get_solde(self): # GETTER : lire le solde
return self.__solde
def set_solde(self, nouveau_solde): # SETTER : modifier
if nouveau_solde >= 0: # on vérifie avant d'accepter
self.__solde = nouveau_solde
else:
print("❌ Le solde ne peut pas être négatif !")
compte = CompteBancaire(1000)
print(compte.get_solde()) # 1000
compte.set_solde(1500) # OK
compte.set_solde(-100) # ❌ Le solde ne peut pas être négatif !
✨ @property — la façon moderne (et élégante !)
@property permet d'utiliser un attribut privé COMME S'IL ÉTAIT public, tout en gardant le
contrôle. C'est la méthode recommandée en Python moderne.
class CompteBancaire:
def __init__(self, solde):
self.__solde = solde
@property # décorateur getter
def solde(self):
"""Le solde du compte."""
return self.__solde
@[Link] # décorateur setter
def solde(self, valeur):
if valeur >= 0:
self.__solde = valeur
else:
raise ValueError("Solde négatif interdit !")
compte = CompteBancaire(1000)
# Avec @property, on écrit comme si c'était un attribut public !
print([Link]) # 1000 (pas de parenthèses !)
[Link] = 2000 # OK, appelle le setter automatiquement
[Link] = -50 # ValueError: Solde négatif interdit !
4. L'Héritage
L'héritage permet à une classe ENFANT de récupérer automatiquement tous les attributs et
méthodes d'une classe PARENT. On évite de réécrire du code !
💡 Analogie C'est comme l'héritage familial : tu hérites des yeux de ta mère et du nez de
ton père, mais tu as aussi tes propres caractéristiques uniques.
🌱 Héritage Simple
# CLASSE PARENT (ou classe de base)
class Animal:
def __init__(self, nom, age):
[Link] = nom
[Link] = age
def manger(self):
print(f"{[Link]} mange.")
def dormir(self):
print(f"{[Link]} dort.")
# CLASSE ENFANT : hérite d'Animal
class Chien(Animal): # 'Animal' entre parenthèses = héritage
def __init__(self, nom, age, race):
super().__init__(nom, age) # appelle le constructeur parent
[Link] = race # attribut propre au Chien
def aboyer(self): # méthode propre au Chien
print(f"{[Link]} : Wouf !")
rex = Chien("Rex", 3, "Berger")
[Link]() # ✅ Hérité d'Animal : Rex mange.
[Link]() # ✅ Hérité d'Animal : Rex dort.
[Link]() # ✅ Propre à Chien : Rex : Wouf !
print([Link]) # ✅ Propre à Chien : Berger
🌳 Héritage Multiple
Python supporte l'héritage depuis PLUSIEURS classes à la fois. La classe enfant hérite des
attributs et méthodes de toutes ses classes parents.
class Volant:
def voler(self):
print("Je vole dans les airs !")
class Nageur:
def nager(self):
print("Je nage dans l'eau !")
# Canard hérite de DEUX classes
class Canard(Volant, Nageur):
def coin_coin(self):
print("Coin coin !")
donald = Canard()
[Link]() # Je vole dans les airs !
[Link]() # Je nage dans l'eau !
donald.coin_coin() # Coin coin !
🔗 super() — appeler le parent
super() permet d'appeler une méthode de la classe parent depuis la classe enfant. C'est
indispensable quand on veut enrichir une méthode sans la réécrire entièrement.
class Animal:
def __init__(self, nom):
[Link] = nom
print(f"Animal créé : {[Link]}")
class Chien(Animal):
def __init__(self, nom, race):
super().__init__(nom) # appelle Animal.__init__
[Link] = race # ajoute ce qui est propre au Chien
print(f"Race : {[Link]}")
rex = Chien("Rex", "Berger")
# Résultat :
# Animal créé : Rex
# Race : Berger
5. Le Polymorphisme
Polymorphisme vient du grec et signifie « plusieurs formes ». En POO, cela signifie qu'une
même méthode peut se comporter DIFFÉREMMENT selon l'objet qui l'appelle.
💡 Analogie Le verbe 'parler' est polymorphe : un Français parle en français, un
Espagnol en espagnol, un bébé balbutie. Même action, comportements différents !
🔄 L'Overriding (Redéfinition)
L'overriding consiste à RÉÉCRIRE une méthode héritée dans la classe enfant pour lui
donner un comportement spécifique.
class Animal:
def parler(self):
print("... (son générique)")
class Chien(Animal):
def parler(self): # OVERRIDE de la méthode parent
print("Wouf !")
class Chat(Animal):
def parler(self): # OVERRIDE différent
print("Miaou !")
class Vache(Animal):
def parler(self): # OVERRIDE encore différent
print("Meuh !")
# Le polymorphisme en action !
animaux = [Chien(), Chat(), Vache(), Animal()]
for a in animaux:
[Link]() # Même appel, comportements différents !
# Résultat :
# Wouf ! / Miaou ! / Meuh ! / ... (son générique)
⚡ Overriding vs Overloading
En Python, l'overloading (plusieurs méthodes avec le même nom mais des paramètres
différents, comme en Java ou C++) n'existe PAS directement. Python gère ça différemment.
# En Java (juste pour comprendre la différence) :
# void parler() { ... } # version 1
# void parler(String langue) { ... } # version 2 = OVERLOADING
# En Python, on utilise des paramètres par défaut à la place :
class Chien:
def aboyer(self, fois=1): # paramètre optionnel
print("Wouf ! " * fois)
rex = Chien()
[Link]() # Wouf !
[Link](3) # Wouf ! Wouf ! Wouf !
# Python offre aussi __add__, __mul__, etc. pour surcharger les opérateurs :
class Vecteur:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, autre): # surcharge du +
return Vecteur(self.x + autre.x, self.y + autre.y)
v1 = Vecteur(1, 2)
v2 = Vecteur(3, 4)
v3 = v1 + v2 # appelle __add__ automatiquement !
print(v3.x, v3.y) # 4 6
6. L'Abstraction
L'abstraction consiste à définir une STRUCTURE OBLIGATOIRE que toutes les classes
enfants doivent respecter, sans implémenter le code dans la classe parent.
💡 Analogie C'est comme un formulaire vierge : il définit les cases à remplir (nom,
prénom, âge), mais laisse chaque personne remplir ses propres informations. La
structure est imposée, le contenu varie.
Classe Abstraite avec le module abc
abc signifie Abstract Base Class. Une classe abstraite ne peut pas être instanciée
directement (on ne peut pas créer d'objet depuis elle). Elle sert de modèle.
from abc import ABC, abstractmethod
class Forme(ABC): # hérite d'ABC = classe abstraite
"""Forme géométrique abstraite."""
@abstractmethod # DOIT être redéfinie dans l'enfant
def aire(self):
"""Calcule l'aire de la forme."""
pass # pas d'implémentation ici
@abstractmethod
def perimetre(self):
"""Calcule le périmètre."""
pass
def decrire(self): # méthode normale (non abstraite)
print(f"Aire={[Link]():.2f}, Périmètre={[Link]():.2f}")
# CLASSES CONCRÈTES qui implémentent les méthodes abstraites
class Cercle(Forme):
def __init__(self, rayon):
[Link] = rayon
def aire(self): # OBLIGÉ de définir cette méthode
return 3.14159 * [Link] ** 2
def perimetre(self):
return 2 * 3.14159 * [Link]
class Rectangle(Forme):
def __init__(self, l, h):
self.l, self.h = l, h
def aire(self):
return self.l * self.h
def perimetre(self):
return 2 * (self.l + self.h)
# Utilisation
formes = [Cercle(5), Rectangle(4, 6)]
for f in formes:
[Link]()
# Résultat : Aire=78.54, Périmètre=31.42
# Résultat : Aire=24.00, Périmètre=20.00
# Impossible d'instancier Forme directement !
f = Forme() # ❌ TypeError: Can't instantiate abstract class
7. Les Méthodes Spéciales : __str__, __repr__,
__dict__, __doc__
Python utilise des méthodes spéciales entourées de doubles underscores (appelées dunder
methods) pour définir des comportements spéciaux sur les objets.
📝 __str__ vs __repr__
Ces deux méthodes contrôlent comment un objet s'affiche, mais avec des objectifs différents
:
• __str__ : affichage lisible pour l'utilisateur final (beau et clair)
• __repr__ : affichage technique pour le développeur (doit permettre de recréer l'objet)
class Livre:
def __init__(self, titre, auteur, annee):
[Link] = titre
[Link] = auteur
[Link] = annee
def __str__(self): # pour print() et str()
return f"📚 {[Link]} - {[Link]}"
def __repr__(self): # pour repr() et dans la console
return f"Livre('{[Link]}', '{[Link]}', {[Link]})"
livre = Livre("1984", "Orwell", 1949)
print(str(livre)) # 📚 1984 - Orwell ← lisible
print(repr(livre)) # Livre('1984','Orwell',1949) ← technique
print(livre) # utilise __str__ : 📚 1984 - Orwell
📦 __dict__
__dict__ est un dictionnaire contenant tous les attributs d'instance d'un objet. Très utile pour
débugger ou sérialiser un objet.
class Personne:
def __init__(self, nom, age, ville):
[Link] = nom
[Link] = age
[Link] = ville
alice = Personne("Alice", 25, "Paris")
print(alice.__dict__)
# {'nom': 'Alice', 'age': 25, 'ville': 'Paris'}
# Très utile pour itérer sur les attributs :
for cle, valeur in alice.__dict__.items():
print(f"{cle} : {valeur}")
# nom : Alice
# age : 25
# ville : Paris
📋 __doc__
__doc__ contient la documentation (docstring) d'une classe ou méthode. C'est la chaîne de
texte écrite entre triples guillemets juste après la définition.
class Voiture:
"""
Représente une voiture.
Attributs: marque, modele, annee
"""
def __init__(self, marque, modele):
[Link] = marque
[Link] = modele
def demarrer(self):
"""Démarre le moteur de la voiture."""
print("Vroom !")
print(Voiture.__doc__)
# Représente une voiture.
# Attributs: marque, modele, annee
print([Link].__doc__)
# Démarre le moteur de la voiture.
# help() utilise __doc__ pour afficher l'aide complète
help(Voiture) # affiche toute la doc de la classe
8. Le Garbage Collector et le Comptage de
Références
💡 Analogie Imagine que chaque objet est une maison avec des locataires. Tant qu'il y a
au moins un locataire, la maison reste debout. Quand tous les locataires partent
(références = 0), Python démolît la maison et libère le terrain (mémoire).
🔢 Le Comptage de Références
Python garde un compteur pour chaque objet : combien de variables y font référence.
Quand ce compteur tombe à 0, l'objet est supprimé automatiquement.
import sys # module pour obtenir le compteur de références
class MonObjet:
def __del__(self): # appelé quand l'objet est détruit
print(" Objet détruit !")
# Créer un objet : compteur = 1
a = MonObjet()
print([Link](a)) # 2 (a + l'argument de getrefcount)
# Ajouter une référence : compteur = 2
b = a # b pointe vers le MÊME objet
print([Link](a)) # 3
# Supprimer une référence
del b # compteur redescend à 2
del a # compteur = 0 → objet détruit !
# Objet détruit !
♻️Le Garbage Collector
Le Garbage Collector (GC) s'occupe des cas plus complexes : les références circulaires.
C'est quand deux objets se pointent mutuellement, créant un cycle qui empêche le compteur
de tomber à 0.
import gc # module Garbage Collector
class Noeud:
def __init__(self, valeur):
[Link] = valeur
[Link] = None # pointeur vers un autre noeud
# Créer une référence circulaire
noeud1 = Noeud(1)
noeud2 = Noeud(2)
[Link] = noeud2 # noeud1 → noeud2
[Link] = noeud1 # noeud2 → noeud1 (cycle !)
del noeud1, noeud2 # compteurs ≠ 0 à cause du cycle
# Sans GC : fuite mémoire !
[Link]() # le GC détecte et nettoie le cycle
print("Mémoire nettoyée ✅")
# Info : le GC tourne automatiquement en arrière-plan
print([Link]()) # True → activé par défaut
Mécanisme Rôle
Comptage de références Principal mécanisme : supprime un objet dès que son compteur
atteint 0
Garbage Collector (gc) Mécanisme secondaire : gère les références circulaires que le
comptage ne peut pas détecter
__del__ Méthode appelée automatiquement juste avant la destruction
d'un objet
✅ Résumé Global — Les Piliers de la POO
Concept Mot-clé Python En une phrase
Classe class NomClasse: Le moule qui définit structure et comportement
Objet obj = MaClasse() Une instance concrète créée depuis une classe
Attribut [Link] = valeur Une donnée stockée dans l'objet
Méthode def Une fonction définie dans une classe
ma_methode(self):
Encapsulation _ ou __ + Protéger les données et contrôler l'accès
@property
Héritage class Une classe récupère les attributs/méthodes d'une autre
Enfant(Parent):
super() super().__init__() Appeler le constructeur ou une méthode du parent
Polymorphisme def methode() dans Même nom de méthode, comportements différents
l'enfant
Abstraction from abc import Imposer une structure sans implémenter le code
ABC
__str__ def __str__(self): Affichage lisible pour print()
__repr__ def Affichage technique pour le développeur
__repr__(self):
__dict__ objet.__dict__ Dictionnaire de tous les attributs d'instance
GC import gc / del Libère automatiquement la mémoire inutilisée
🎯 La POO c'est une façon de PENSER le code : au lieu de fonctions qui manipulent
des données séparées, tu crées des OBJETS autonomes qui regroupent données et
comportements. Cela rend le code plus organisé, réutilisable, et facile à faire
évoluer.
Félicitations ! Tu maîtrises maintenant tous les concepts fondamentaux de la POO en
Python. 🎉