La Programmation Orientée Objet (POO) avec Python
Dernière mise à jour : Novembre 2025
Parcours d'apprentissage
Partie 1 : Les Fondamentaux de la POO
Partie 3 : Concepts Avancés et Magie Python
Partie 4 : Ateliers de Synthèse
Partie 1 : Les Fondamentaux de la POO
Chapitre 1 : Pourquoi la POO ? De la procédure à l'objet
Jusqu'à présent, vous avez programmé de manière procédurale : une suite d'instructions et de fonctions qui manipulent des données. Cette approche fonctionne bien pour des scripts simples, mais devient difficile à maintenir lorsque les programmes grandissent. La Programmation Orientée Objet (POO) propose une nouvelle façon de penser : au lieu de séparer les données des fonctions qui les traitent, on les regroupe dans des entités logiques appelées objets.
Organisation du Code
La POO permet de structurer le code en "boîtes" logiques et autonomes. Un objet "Voiture" contient ses propres données (couleur, marque) et ses propres actions (démarrer, accélérer). C'est plus clair et plus facile à gérer.
Réutilisabilité
Une fois qu'un "moule" à objet (une classe) est créé, on peut l'utiliser pour fabriquer autant d'objets que l'on veut. On peut aussi créer de nouveaux moules basés sur les anciens, en héritant de leurs fonctionnalités sans avoir à tout réécrire.
Chapitre 2 : Les Classes et les Objets
Les deux concepts centraux de la POO sont la classe et l'objet.
- La Classe : C'est le plan de construction, le moule. Elle définit la structure commune à tous les objets d'un certain type : les données qu'ils contiendront (les attributs) et les actions qu'ils pourront effectuer (les méthodes).
- L'Objet : C'est une instance de la classe. C'est le produit fini, créé à partir du plan. Si `Voiture` est la classe, alors ma voiture bleue et votre voiture rouge sont deux objets distincts de la même classe.
Illustration 1
Illustration 2
2.1. Déclarer une classe et créer un objet
En Python, on utilise le mot-clé `class` pour définir une classe. Par convention, les noms de classes commencent par une majuscule. Pour créer un objet (instancier la classe), on appelle la classe comme une fonction.
# 1. Définition de la classe (le plan)
class Chien:
pass # 'pass' signifie que la classe est vide pour l'instant
# 2. Création des objets (les instances)
mon_chien = Chien()
autre_chien = Chien()
# On peut vérifier le type de nos objets
print(type(mon_chien))
print(type(autre_chien))
Chapitre 3 : Le Constructeur et les Attributs d'instance
Nos objets sont pour l'instant des coquilles vides. Pour qu'ils aient leurs propres données (un nom, une couleur, un âge...), nous devons définir des attributs d'instance. Cela se fait généralement dans une méthode spéciale appelée le constructeur.
Illustration 3
3.1. La méthode `__init__` et le paramètre `self`
Le constructeur en Python est une méthode qui s'appelle `__init__` (avec deux tirets bas avant et après). Elle est exécutée automatiquement chaque fois qu'un nouvel objet est créé. Son premier paramètre est toujours `self`.
Le mot-clé `self` représente l'objet lui-même. Il permet, à l'intérieur de la classe, de faire référence à l'instance en cours de création ou d'utilisation. On l'utilise pour attacher des données à l'objet. On parle alors d'attributs d'instance (ex: `self.nom`).
class Voiture:
# Le constructeur
def __init__(self, marque_voiture, couleur_voiture):
# On crée les attributs d'instance en les attachant à 'self'
self.marque = marque_voiture
self.couleur = couleur_voiture
print(f"Une voiture {self.marque} de couleur {self.couleur} a été créée !")
# On crée des objets en passant les arguments attendus par __init__ (sauf self)
voiture_1 = Voiture("Renault", "bleue")
voiture_2 = Voiture("Peugeot", "rouge")
# On peut accéder aux attributs de chaque objet avec la notation pointée
print(f"La première voiture est une {voiture_1.marque}.")
print(f"La seconde voiture est de couleur {voiture_2.couleur}.")
Chapitre 4 : Les Méthodes, le comportement des objets
Les méthodes sont des fonctions définies à l'intérieur d'une classe. Elles définissent les actions que les objets de cette classe peuvent effectuer. Tout comme le constructeur, leur premier paramètre est toujours `self`, ce qui leur donne accès aux attributs et aux autres méthodes de l'objet.
4.1. Définir et appeler une méthode
class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
# Une méthode d'instance simple
def se_presenter(self):
print(f"Bonjour, je m'appelle {self.nom} et j'ai {self.age} ans.")
# Une méthode qui modifie un attribut de l'instance
def vieillir(self):
self.age += 1
print(f"Joyeux anniversaire ! {self.nom} a maintenant {self.age} ans.")
# Création d'un objet
p1 = Personne("Ali", 30)
# Appel des méthodes sur l'objet
p1.se_presenter() # Affiche: Bonjour, je m'appelle Ali et j'ai 30 ans.
p1.vieillir() # Affiche: Joyeux anniversaire ! Ali a maintenant 31 ans.
p1.se_presenter() # Affiche: Bonjour, je m'appelle Ali et j'ai 31 ans.
Ateliers Pratiques : Vos premières classes
Il est temps de mettre en pratique ces concepts fondamentaux en créant vos propres classes.
Exercice 1 : La classe `Point`
1. Créez une classe `Point` qui prend deux coordonnées `x` et `y` lors de sa création.
2. Ajoutez une méthode `afficher()` qui affiche les coordonnées sous la forme `(x, y)`.
3. Créez deux instances de `Point` avec des coordonnées différentes et appelez leur méthode `afficher()`.
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def afficher(self):
print(f"({self.x}, {self.y})")
# Création et utilisation des objets
p1 = Point(2, 3)
p2 = Point(-4, 10)
print("Coordonnées du point 1 :")
p1.afficher() # Affiche (2, 3)
print("Coordonnées du point 2 :")
p2.afficher() # Affiche (-4, 10)
Exercice 2 : La classe `Livre`
1. Créez une classe `Livre` avec les attributs `titre`, `auteur` et `nb_pages`.
2. Ajoutez une méthode `description()` qui retourne une chaîne de caractères décrivant le livre : `"Le livre '[titre]' par [auteur] contient [nb_pages] pages."`.
3. Créez un objet de type `Livre` et affichez sa description.
class Livre:
def __init__(self, titre, auteur, nb_pages):
self.titre = titre
self.auteur = auteur
self.nb_pages = nb_pages
def description(self):
return f"Le livre '{self.titre}' par {self.auteur} contient {self.nb_pages} pages."
# Création et utilisation
livre_python = Livre("Python pour les Nuls", "John Paul Mueller", 432)
description_livre = livre_python.description()
print(description_livre)
Partie 2 : Les Piliers de la POO
Chapitre 5 : L'Encapsulation et la visibilité des attributs
L'encapsulation est le premier pilier de la POO. C'est l'idée de regrouper les données (attributs) et les méthodes qui les manipulent au sein d'un même objet. Mais cela va plus loin : l'encapsulation vise aussi à protéger les données d'un accès ou d'une modification non contrôlée depuis l'extérieur de l'objet. On parle de "cacher les données" (data hiding).
Illustration 4
Illustration 5
5.1. Attributs publics, protégés et privés
En Python, la visibilité est une convention :
- Public : `nom_attribut`. Accessible de partout. C'est le comportement par défaut.
- Protégé : `_nom_attribut` (un seul underscore). Indique que l'attribut ne devrait pas être modifié depuis l'extérieur, mais est destiné aux classes filles (voir héritage).
- Privé : `__nom_attribut` (deux underscores). Python "mutile" le nom de cet attribut pour le rendre très difficile d'accès depuis l'extérieur, le réservant à un usage strictement interne à la classe.
Illustration 6
Illustration 7
Illustration 8
class CompteBancaire:
def __init__(self, titulaire, solde_initial):
self.titulaire = titulaire # Attribut public
self.__solde = solde_initial # Attribut privé
# Méthode publique pour déposer de l'argent (contrôle l'accès à __solde)
def deposer(self, montant):
if montant > 0:
self.__solde += montant
print(f"{montant} DH déposés. Nouveau solde : {self.__solde} DH.")
else:
print("Le montant du dépôt doit être positif.")
# Méthode publique pour consulter le solde (un "getter")
def get_solde(self):
return self.__solde
compte = CompteBancaire("Fatima", 1000)
# On peut accéder à l'attribut public
print(f"Titulaire : {compte.titulaire}")
# On NE PEUT PAS accéder directement à l'attribut privé
# print(compte.__solde) # -> Ceci lèvera une AttributeError
# On utilise les méthodes publiques pour interagir avec le solde
print(f"Solde initial : {compte.get_solde()} DH.")
compte.deposer(500)
Chapitre 6 : L'Héritage, réutiliser et étendre
L'héritage est le deuxième pilier de la POO. Il permet de créer une nouvelle classe (la classe fille ou sous-classe) qui hérite des attributs et méthodes d'une classe existante (la classe mère ou super-classe). C'est le principe de la relation "est un" : un `Chien` est un `Animal`.
Illustration 9
Illustration 10
Illustration 11
6.1. Héritage simple et `super()`
Pour faire hériter une classe, on indique le nom de la classe mère entre parenthèses. La classe fille a alors accès à toutes les méthodes de sa mère. Pour appeler une méthode de la classe mère (notamment le constructeur) depuis la classe fille, on utilise la fonction `super()`.
# Classe Mère
class Animal:
def __init__(self, nom):
self.nom = nom
def manger(self):
print(f"{self.nom} est en train de manger.")
# Classe Fille qui hérite de Animal
class Chien(Animal):
def __init__(self, nom, race):
# On appelle le constructeur de la classe mère (Animal) pour initialiser 'nom'
super().__init__(nom)
self.race = race
# Méthode spécifique à la classe Chien
def aboyer(self):
print("Wouaf ! Wouaf !")
mon_animal = Animal("Générique")
mon_chien = Chien("Rex", "Berger Allemand")
# Le chien peut manger, car il a hérité cette méthode de Animal
mon_chien.manger()
# Le chien peut aboyer, car c'est sa propre méthode
mon_chien.aboyer()
# L'animal générique ne peut pas aboyer
# mon_animal.aboyer() # -> AttributeError
Chapitre 7 : Le Polymorphisme, un nom pour plusieurs formes
Le polymorphisme (du grec "plusieurs formes") est la capacité pour des objets de classes différentes de répondre à un même message (un même appel de méthode) de manière spécifique. Concrètement, si les classes `Chien` et `Chat` héritent toutes deux de `Animal` et ont toutes deux une méthode `crier()`, l'appel à cette méthode produira un son différent pour chaque objet.
Illustration 12
Illustration 13
7.1. Surcharge de méthode (Overriding)
On implémente le polymorphisme en surchargeant les méthodes de la classe mère. Cela signifie qu'on redéfinit dans la classe fille une méthode qui existe déjà dans la classe mère.
class Animal:
def crier(self):
print("L'animal crie...")
class Chien(Animal):
# On surcharge la méthode crier() de la classe Animal
def crier(self):
print("Wouaf !")
class Chat(Animal):
# On surcharge aussi la méthode crier()
def crier(self):
print("Miaou !")
chien = Chien()
chat = Chat()
# Le même appel de méthode 'crier()' produit un résultat différent
chien.crier() # Affiche "Wouaf !"
chat.crier() # Affiche "Miaou !"
Ateliers Pratiques : Les Piliers de la POO
Appliquons ces trois piliers pour construire des hiérarchies de classes logiques et robustes.
Exercice : Véhicules et Héritage
1. Créez une classe mère `Vehicule` avec un constructeur qui initialise `marque` et `annee`. Elle doit aussi avoir une méthode `decrire()` qui affiche ces informations.
2. Créez une classe fille `Voiture` qui hérite de `Vehicule`. Son constructeur doit prendre en plus un `nombre_portes`. La méthode `decrire()` de la voiture doit aussi afficher le nombre de portes.
3. Créez une classe fille `Moto` qui hérite de `Vehicule`. Son constructeur doit prendre en plus un booléen `side_car`. La méthode `decrire()` de la moto doit indiquer si elle a un side-car ou non.
class Vehicule:
def __init__(self, marque, annee):
self.marque = marque
self.annee = annee
def decrire(self):
print(f"Véhicule de marque {self.marque}, année {self.annee}.", end=" ")
class Voiture(Vehicule):
def __init__(self, marque, annee, nombre_portes):
super().__init__(marque, annee)
self.nombre_portes = nombre_portes
# Surcharge de la méthode decrire()
def decrire(self):
super().decrire()
print(f"C'est une voiture avec {self.nombre_portes} portes.")
class Moto(Vehicule):
def __init__(self, marque, annee, side_car):
super().__init__(marque, annee)
self.side_car = side_car
def decrire(self):
super().decrire()
if self.side_car:
print("C'est une moto avec un side-car.")
else:
print("C'est une moto sans side-car.")
ma_voiture = Voiture("Dacia", 2021, 5)
ma_moto = Moto("Yamaha", 2019, False)
ma_voiture.decrire()
ma_moto.decrire()
Partie 3 : Concepts Avancés et Magie Python
Chapitre 8 : Les Méthodes Spéciales (Dunder)
Vous avez déjà rencontré une méthode spéciale : `__init__`. Python est rempli de ces méthodes, appelées "dunder methods" (pour Double UNDERscore). Elles permettent à nos objets de s'intégrer de manière transparente avec les fonctionnalités natives du langage, comme l'affichage avec `print()`, la comparaison avec `==`, ou l'addition avec `+`. Elles rendent notre code plus "pythonique".
8.1. `__str__` et `__repr__` : Représentations de l'objet
Ces deux méthodes permettent de définir comment un objet doit être représenté sous forme de chaîne de caractères :
__str__: Fournit une représentation lisible pour l'utilisateur final. C'est elle qui est appelée par `print()` et `str()`.__repr__: Fournit une représentation non ambiguë pour le développeur. L'idée est que cette chaîne puisse, si possible, recréer l'objet.
class Livre:
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
def __str__(self):
return f"'{self.titre}' par {self.auteur}"
def __repr__(self):
return f"Livre(titre='{self.titre}', auteur='{self.auteur}')"
mon_livre = Livre("1984", "George Orwell")
# __str__ est utilisée par print()
print(mon_livre)
# __repr__ est utilisée quand on affiche l'objet directement
mon_livre
Chapitre 9 : Attributs et Méthodes de Classe/Statiques
Jusqu'ici, nous n'avons manipulé que des attributs et méthodes d'instance, qui sont propres à chaque objet. Mais il existe aussi des attributs et méthodes qui sont liés à la classe elle-même.
9.1. Attributs de classe
Un attribut de classe est une variable partagée par toutes les instances de cette classe. Il est défini directement dans la classe, en dehors de toute méthode. C'est idéal pour des constantes ou des compteurs.
class Voiture:
# Attribut de classe : partagé par tous les objets Voiture
nombre_de_roues = 4
def __init__(self, marque):
self.marque = marque # Attribut d'instance
v1 = Voiture("Renault")
v2 = Voiture("Peugeot")
# On peut y accéder via la classe ou une instance
print(f"Une voiture a {Voiture.nombre_de_roues} roues.")
print(f"La voiture v1 a {v1.nombre_de_roues} roues.")
9.2. Méthodes de classe (`@classmethod`) et statiques (`@staticmethod`)
- Une méthode de classe est décorée avec `@classmethod`. Son premier paramètre n'est pas `self` mais `cls` (qui représente la classe elle-même). Elle est souvent utilisée pour créer des "usines à objets" (factory methods).
- Une méthode statique est décorée avec `@staticmethod`. Elle ne prend ni `self` ni `cls` en premier paramètre. C'est une simple fonction utilitaire qui a un lien logique avec la classe, mais qui ne dépend ni de la classe, ni d'une instance.
class Calculateur:
def __init__(self, valeur):
self.valeur = valeur
# Méthode d'instance : dépend de l'objet 'self'
def ajouter(self, x):
return self.valeur + x
@classmethod
def creer_depuis_chaine(cls, chaine_de_valeur):
# Cette méthode "usine" crée un objet de la classe 'cls'
valeur = int(chaine_de_valeur)
return cls(valeur)
@staticmethod
def info():
# Fonction utilitaire qui n'a pas besoin de 'self' ou 'cls'
print("Ceci est une classe de calcul simple.")
# Utilisation de la méthode de classe comme factory
calc = Calculateur.creer_depuis_chaine("10")
print(calc.ajouter(5)) # Affiche 15
# Utilisation de la méthode statique
Calculateur.info()
Ateliers Pratiques : Concepts Avancés
Exercice : Améliorer la classe `CompteBancaire`
Reprenons la classe `CompteBancaire` :
1. Ajoutez une méthode `__str__` qui retourne une chaîne comme `"Compte de [Titulaire] - Solde : [Solde] DH"`.
2. Ajoutez un attribut de classe `nombre_comptes` qui s'incrémente à chaque fois qu'un nouveau compte est créé.
3. Ajoutez une méthode de classe `afficher_nombre_comptes()` qui affiche le nombre total de comptes créés.
class CompteBancaire:
nombre_comptes = 0
def __init__(self, titulaire, solde_initial):
self.titulaire = titulaire
self.__solde = solde_initial
CompteBancaire.nombre_comptes += 1
def __str__(self):
return f"Compte de {self.titulaire} - Solde : {self.__solde} DH"
@classmethod
def afficher_nombre_comptes(cls):
print(f"Nombre total de comptes créés : {cls.nombre_comptes}")
print("Création des comptes...")
c1 = CompteBancaire("Ali", 2000)
c2 = CompteBancaire("Samira", 5000)
print(c1)
print(c2)
CompteBancaire.afficher_nombre_comptes() # Affiche: Nombre total de comptes créés : 2
Partie 4 : Ateliers de Synthèse
Chapitre 10 : Projet Pratique - Mini-système de gestion de bibliothèque
Il est temps de rassembler toutes les compétences que vous avez acquises. Nous allons créer un petit programme en POO pour gérer une bibliothèque. Ce projet utilisera des classes, des objets, des méthodes, des attributs privés et des interactions entre objets.
Objectifs du projet :
- Créer une classe `Livre` avec un titre, un auteur et un statut (disponible/emprunté).
- Créer une classe `Bibliotheque` qui contient une collection de livres.
- La `Bibliotheque` doit permettre d'ajouter un livre, d'emprunter un livre (en vérifiant sa disponibilité), de retourner un livre et de lister tous les livres avec leur statut.
- Utiliser la méthode spéciale `__str__` pour afficher joliment les informations d'un livre.
class Livre:
"""Représente un livre avec un titre, un auteur et un statut."""
def __init__(self, titre, auteur):
self.titre = titre
self.auteur = auteur
self.est_emprunte = False
def __str__(self):
# utilisation de condition normale
# if self.est_emprunte:
# statut = "Emprunté"
# else:
# statut = "Disponible"
# affectation conditionnelle au lieu de la condition if normale
# statut = "Emprunté" if self.est_emprunte else "Disponible"
return f"'{self.titre}' par {self.auteur} - Statut : {statut}"
class Bibliotheque:
"""Gère une collection de livres."""
def __init__(self, nom):
self.nom = nom
self.__catalogue = [] # La liste des livres est privée
def ajouter_livre(self, livre):
self.__catalogue.append(livre)
print(f"'{livre.titre}' a été ajouté au catalogue.")
def lister_livres(self):
print(f"\n--- Livres dans la bibliothèque '{self.nom}' ---")
for livre in self.__catalogue:
print(livre)
def emprunter_livre(self, titre):
for livre in self.__catalogue:
if livre.titre == titre:
if not livre.est_emprunte:
livre.est_emprunte = True
print(f"Vous avez emprunté '{titre}'.")
else:
print(f"Désolé, '{titre}' est déjà emprunté.")
return
print(f"Le livre '{titre}' n'a pas été trouvé dans notre catalogue.")
def retourner_livre(self, titre):
for livre in self.__catalogue:
if livre.titre == titre:
livre.est_emprunte = False
print(f"Merci d'avoir retourné '{titre}'.")
return
# --- Programme principal ---
ma_biblio = Bibliotheque("Bibliothèque Municipale")
ma_biblio.ajouter_livre(Livre("L'Étranger", "Albert Camus"))
ma_biblio.ajouter_livre(Livre("1984", "George Orwell"))
ma_biblio.lister_livres()
ma_biblio.emprunter_livre("1984")
ma_biblio.emprunter_livre("Livre Inconnu")
ma_biblio.emprunter_livre("1984") # Essayer d'emprunter un livre déjà pris
ma_biblio.lister_livres() # Montre le changement de statut
ma_biblio.retourner_livre("1984")
ma_biblio.lister_livres() # Le livre est de nouveau disponible
Examen Régional 2024/2025
Mettez en pratique vos connaissances en Programmation Orientée Objet avec ces sujets d'examen.
Variante 1 : Système de gestion de notes pour une école (15 pts)
Vous êtes chargé de développer un système pour aider les professeurs d'une école à suivre les notes de leurs étudiants. Le programme doit permettre d'ajouter des étudiants, d'enregistrer leurs notes dans différentes matières, et de calculer des statistiques.
Énoncé :
1. Créer une classe Etudiant : (7 pts)
- Attributs : nom, prenom, niveau, notes (dictionnaire avec les matières comme clés et les notes comme valeurs). (2 pts)
- Méthodes :
ajouter_note(matiere, note): ajoute une note pour une matière donnée. (2 pts)calculer_moyenne(): retourne la moyenne des notes de l'étudiant. (3 pts)
2. Créer une classe Classe : (8 pts)
- Attributs : nom_classe, liste_etudiants. (1 pt)
- Méthodes :
ajouter_etudiant(etudiant): ajoute un étudiant à la classe. (2 pts)moyenne_classe(): calcule la moyenne des moyennes de tous les étudiants. (3 pts)meilleur_etudiant(): affiche l'étudiant avec la meilleure moyenne. (2 pts)
class Etudiant:
"""
Représente un étudiant avec ses notes.
"""
def __init__(self, nom, prenom, niveau):
self.nom = nom
self.prenom = prenom
self.niveau = niveau
self.notes = {} # {matiere: note}
def ajouter_note(self, matiere, note):
"""
Ajoute ou met à jour la note pour une matière donnée.
"""
if isinstance(note, (int, float)) and 0 <= note <= 20:
self.notes[matiere] = note
else:
print(f"Note invalide: {note} doit être entre 0 et 20.")
def calculer_moyenne(self):
"""
Retourne la moyenne des notes de l'étudiant.
"""
if not self.notes:
return 0.0
# solution élégante avec les listes des compréhensions
# return sum([note for note in self.notes.values()]) / len(self.notes)
# Accumulation sans listes de compréhension
somme_notes = 0
nombre_notes = 0
for note in self.notes.values():
somme_notes += note
nombre_notes += 1
return somme_notes / nombre_notes
def __str__(self):
moyenne = self.calculer_moyenne()
return f"Etudiant: {self.prenom} {self.nom}, Niveau: {self.niveau}, Moyenne: {moyenne:.2f}"
class Classe:
"""
Gère un groupe d'étudiants.
"""
def __init__(self, nom_classe):
self.nom_classe = nom_classe
self.liste_etudiants = [] # Liste d'objets Etudiant
def ajouter_etudiant(self, etudiant):
"""
Ajoute un objet Etudiant à la liste de la classe.
"""
if isinstance(etudiant, Etudiant):
self.liste_etudiants.append(etudiant)
else:
print("Erreur: L'objet ajouté n'est pas un Etudiant valide.")
def moyenne_classe(self):
"""
Calcule la moyenne des moyennes de tous les étudiants.
"""
if not self.liste_etudiants:
return 0.0
# solution élégante avec les listes des compréhensions
# return sum([e.calculer_moyenne() for e in self.liste_etudiants]) / len(self.liste_etudiants)
# Accumulation sans listes de compréhension
somme_moyennes = 0.0
nombre_etudiants = 0
for etudiant in self.liste_etudiants:
somme_moyennes += etudiant.calculer_moyenne()
nombre_etudiants += 1
return somme_moyennes / nombre_etudiants
def meilleur_etudiant(self):
"""
Affiche l'étudiant avec la meilleure moyenne.
"""
if not self.liste_etudiants:
print(f"La classe {self.nom_classe} est vide.")
return None
# solution élégante avec les listes des compréhensions
# meilleur_etudiant = max(self.liste_etudiants, key=lambda e: e.calculer_moyenne())
meilleur_etudiant = None
meilleure_moyenne = -1 # Initialisation à une valeur impossible
# Itération et comparaison explicite
for etudiant in self.liste_etudiants:
moyenne_actuelle = etudiant.calculer_moyenne()
if moyenne_actuelle > meilleure_moyenne:
meilleure_moyenne = moyenne_actuelle
meilleur_etudiant = etudiant
print(f"\n--- Meilleur étudiant de la classe {self.nom_classe} ---")
print(meilleur_etudiant)
print("-----------------------------------------------------")
return meilleur_etudiant
# Création des étudiants
e1 = Etudiant("Hassani", "youssef", "TS")
e2 = Etudiant("karimi", "Amal", "TS")
# Ajout des notes
e1.ajouter_note("Maths", 15.5)
e1.ajouter_note("Physique", 17.0)
e2.ajouter_note("Maths", 10.0)
e2.ajouter_note("Physique", 12.5)
# Test des moyennes individuelles
print(f"Moyenne de Youssef: {e1.calculer_moyenne():.2f}")
print(f"Moyenne de Amal: {e2.calculer_moyenne():.2f}")
# Création et gestion de la classe
classe_TS = Classe("Terminale S - A")
classe_TS.ajouter_etudiant(e1)
classe_TS.ajouter_etudiant(e2)
# Test des statistiques de classe
print(f"\nMoyenne générale de la classe : {classe_TS.moyenne_classe():.2f}")
classe_TS.meilleur_etudiant()
Variante 2 : Calculateur de gestion de stock pour un magasin (15 pts)
Un magasin souhaite automatiser la gestion de son inventaire. Le programme doit pouvoir ajouter de nouveaux produits, mettre à jour les quantités en stock, et générer des rapports sur l'état des stocks.
Énoncé :
1. Créer une classe Produit : (7 pts)
- Attributs : nom, prix, quantite_en_stock. (2 pts)
- Méthodes :
approvisionner(quantite): ajoute une quantité au stock existant. (2 pts)vendre(quantite): réduit le stock en fonction de la quantité vendue, en vérifiant qu'il y a suffisamment de stock. (3 pts)
2. Créer une classe Magasin : (8 pts)
- Attributs : nom, adresse, catalogue (liste des produits). (1 pt)
- Méthodes :
ajouter_produit(produit): ajoute un produit au catalogue. (2 pts)rechercher_produit(nom): recherche un produit dans le catalogue. (2 pts)generer_rapport_stock(): affiche un rapport avec tous les produits en stock et leur quantité. (3 pts)
class Produit:
"""
Représente un produit avec son nom, son prix unitaire et sa quantité en stock.
"""
def __init__(self, nom, prix, quantite_en_stock):
"""Initialise les attributs du produit."""
self.nom = nom
self.prix = prix
# Assure que le stock initial est non négatif
self.quantite_en_stock = max(0, quantite_en_stock)
def approvisionner(self, quantite):
"""
Ajoute la quantité spécifiée au stock.
"""
if quantite > 0:
self.quantite_en_stock += quantite
print(f"Stock de {self.nom} mis à jour : +{quantite}. Nouveau stock : {self.quantite_en_stock}")
else:
print("Erreur : La quantité d'approvisionnement doit être positive.")
def vendre(self, quantite):
"""
Réduit le stock si la quantité est disponible.
Retourne True si la vente est réussie, False sinon.
"""
if quantite <= 0:
print("Erreur : La quantité vendue doit être positive.")
return False
if self.quantite_en_stock >= quantite:
self.quantite_en_stock -= quantite
montant_vente = quantite * self.prix
print(f"Vente de {quantite} unités de {self.nom} réussie. Montant : {montant_vente:.2f}€. Stock restant : {self.quantite_en_stock}")
return True
else:
print(f"Vente échouée pour {self.nom} : stock insuffisant ({self.quantite_en_stock} disponibles, {quantite} demandés).")
return False
def __str__(self):
"""Permet un affichage lisible du produit."""
return f"Produit: {self.nom} | Prix: {self.prix:.2f}€ | Stock: {self.quantite_en_stock}"
class Magasin:
"""
Représente un magasin gérant un catalogue de produits.
"""
def __init__(self, nom, adresse):
"""Initialise le magasin."""
self.nom = nom
self.adresse = adresse
self.catalogue = [] # Liste d'objets Produit
def ajouter_produit(self, produit):
"""
Ajoute un objet Produit au catalogue du magasin.
"""
if isinstance(produit, Produit):
self.catalogue.append(produit)
print(f"Produit '{produit.nom}' ajouté au catalogue de {self.nom}.")
else:
print("Erreur : L'objet à ajouter n'est pas un Produit valide.")
def rechercher_produit(self, nom):
"""
Recherche un produit par son nom dans le catalogue.
Utilise une boucle 'for' simple.
Retourne l'objet Produit s'il est trouvé, None sinon.
"""
nom_recherche = nom.lower()
# Liste de compréhension + next()
# produit = next(
# (p for p in self.catalogue if p.nom.lower() == nom_recherche),
# None
# )
# Parcours du catalogue avec une boucle ordinaire
for produit in self.catalogue:
if produit.nom.lower() == nom_recherche:
print(f"Produit trouvé : {produit}")
return produit
print(f"Produit '{nom}' non trouvé dans le catalogue.")
return None
def generer_rapport_stock(self):
"""
Affiche un rapport détaillé sur l'état des stocks.
Utilise une boucle 'for' simple pour l'affichage.
"""
print(f"\n======== RAPPORT DE STOCK POUR {self.nom.upper()} ========")
print(f"Localisation : {self.adresse}")
# Affichage direct avec une liste de compréhension (pour l’itération)
#[
# print(f"* {p.nom.ljust(20)} | Quantité: {p.quantite_en_stock} | Prix: {p.prix:.2f}€")
# for p in self.catalogue
#]
# Variables pour le résumé (calculé avec une boucle)
nombre_total_produits = 0
valeur_totale_stock = 0.0
# Boucle ordinaire pour itérer sur le catalogue et générer le rapport
for produit in self.catalogue:
# Affichage du détail
print(f"* {produit.nom.ljust(20)} | Quantité: {produit.quantite_en_stock} | Prix: {produit.prix:.2f}€")
# Calcul du résumé
nombre_total_produits += 1
valeur_totale_stock += produit.quantite_en_stock * produit.prix
print("--------------------------------------------------")
print(f"Total de produits différents : {nombre_total_produits}")
print(f"Valeur totale estimée du stock : {valeur_totale_stock:.2f}€")
print("==================================================\n")
def __str__(self):
return f"Magasin : {self.nom} situé à {self.adresse}. Catalogue contient {len(self.catalogue)} produits différents."
# 1. Création et gestion des produits
pomme = Produit("Pomme", 0.50, 100)
lait = Produit("Lait", 1.20, 50)
pain = Produit("Pain", 2.00, 20)
print("--- Tests de la classe Produit ---")
pomme.vendre(10) # Vente réussie
lait.vendre(60) # Vente échouée (stock insuffisant)
pomme.approvisionner(20)
print(pomme) # Affichage du stock mis à jour
print("\n--- Tests de la classe Magasin ---")
# 2. Création et gestion du magasin
mon_magasin = Magasin("Epicerie du Coin", "12 Rue Principale")
mon_magasin.ajouter_produit(pomme)
mon_magasin.ajouter_produit(lait)
mon_magasin.ajouter_produit(pain)
# 3. Recherche de produit
produit_trouve = mon_magasin.rechercher_produit("Lait")
produit_introuvable = mon_magasin.rechercher_produit("Pâtes")
# 4. Génération du rapport
mon_magasin.generer_rapport_stock()
Conclusion & Perspectives
Félicitations ! Vous avez maîtrisé les concepts fondamentaux de la Programmation Orientée Objet en Python.
En comprenant les classes, les objets et les grands piliers de la POO, vous avez acquis un socle indispensable pour concevoir des applications robustes et évolutives. Mais votre parcours de développeur ne fait que commencer.
Cette maîtrise de la POO est la clé qui vous ouvre désormais les portes de l'écosystème de la Data Science. Vous êtes parfaitement préparés pour aborder les bibliothèques essentielles comme NumPy, Pandas et Matplotlib, et ainsi vous lancer dans les domaines passionnants du Machine Learning et du Deep Learning. Préparez-vous, la prochaine étape de votre aventure ne fait que commencer !