Programmation Python — Séance 5 : Programmation orientée objet

Jusqu’ici, les données (variables) et les traitements (fonctions) étaient séparés.
Télécharger le notebook Ouvrir dans Colab Les sorties sont celles enregistrées dans le notebook : rien n'est réexécuté.

DMI · FST · UCAD

Jusqu’ici, les données (variables) et les traitements (fonctions) étaient séparés. La programmation orientée objet les réunit : un objet regroupe des données et les comportements qui vont avec. Cette séance construit ses propres objets avec class, puis les spécialise par héritage et polymorphisme. Exécutez chaque cellule avec Maj + Entrée. Les cellules À vous sont à compléter.


1. Vous utilisez déjà des objets

Une chaîne, une liste sont déjà des objets : ils portent des données (leur contenu) et des méthodes (des fonctions attachées, appelées avec un point). "dakar".upper() et notes.append(15) sont des méthodes d’objets que vous employez depuis le début.

texte = "dakar"
print(texte.upper())

notes = [17, 12]
notes.append(15)
print(notes)
DAKAR
[17, 12, 15]

À vous. Avec mot = "SENEGAL", affichez sa version en minuscules grâce à la méthode .lower().

# À vous : avec mot = "SENEGAL", affichez mot.lower()

Une méthode peut aussi renvoyer un résultat. "Dakar-Thies-Mbour".split("-") renvoie une liste.

villes = "Dakar-Thies-Mbour".split("-")
print(villes)
print(len(villes))
['Dakar', 'Thies', 'Mbour']
3

À vous. Découpez la chaîne "Astou;Modou;Bineta" sur le ";" avec .split(";") et affichez la liste obtenue.

# À vous : affichez "Astou;Modou;Bineta".split(";")

L’idée de la POO est d’écrire nos propres types d’objets, adaptés à notre problème — un compte bancaire, un patient, un produit — avec leurs données et leurs méthodes.


2. Définir une classe

Une classe est le plan d’un objet : elle décrit ses données et ses méthodes. On la définit avec class. La méthode spéciale __init__ est le constructeur : elle s’exécute à la création et installe les données de l’objet. Le premier paramètre, self, désigne l’objet en cours de construction.

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

c1 = CompteBancaire("Astou", 5000)
print(c1.titulaire)
print(c1.solde)
Astou
5000

c1 est une instance de la classe CompteBancaire. On accède à ses données (ses attributs) avec un point : c1.titulaire, c1.solde. Chaque instance possède ses propres valeurs.

c2 = CompteBancaire("Modou", 12000)
print(c1.titulaire, c1.solde)
print(c2.titulaire, c2.solde)
Astou 5000
Modou 12000

À vous. Créez une instance c3 de CompteBancaire pour le titulaire "Bineta" avec un solde de 8000, puis affichez son titulaire et son solde.

# À vous : creez c3 = CompteBancaire("Bineta", 8000), puis affichez c3.titulaire et c3.solde

Un attribut se lit, mais se modifie aussi directement avec un point.

c2.solde = 20000
print(c2.solde)
20000

À vous. Modifiez le solde de c3 pour le porter à 9500, puis affichez-le.

# À vous : mettez c3.solde a 9500, puis affichez c3.solde

3. Les méthodes

Une méthode est une fonction définie dans la classe : c’est un comportement de l’objet. Elle reçoit toujours self en premier paramètre, ce qui lui permet de lire et de modifier les attributs. Ici, depot et retrait modifient le solde ; peut_retirer renvoie un booléen.

class CompteBancaire:
    def __init__(self, titulaire, solde):
        self.titulaire = titulaire
        self.solde = solde
    def depot(self, montant):
        self.solde = self.solde + montant
    def retrait(self, montant):
        if montant <= self.solde:
            self.solde = self.solde - montant
        else:
            print("Solde insuffisant")
    def peut_retirer(self, montant):
        return montant <= self.solde

c1 = CompteBancaire("Astou", 5000)
c1.depot(2000)
print(c1.solde)
c1.retrait(1000)
print(c1.solde)
7000
6000

On appelle une méthode sur l’objet : c1.depot(2000). Inutile de passer self, Python s’en charge : self sera c1. Si le retrait dépasse le solde, la méthode affiche un message au lieu de l’effectuer.

c1.retrait(50000)
print(c1.solde)
Solde insuffisant
6000

La méthode peut_retirer ne modifie rien : elle renvoie True ou False, qu’on peut utiliser dans un test.

print(c1.peut_retirer(1000))
print(c1.peut_retirer(999999))
True
False

À vous. Créez un compte c4 pour "Modou" avec 3000, effectuez un dépôt de 1500, puis affichez son solde.

# À vous : creez c4 = CompteBancaire("Modou", 3000), faites c4.depot(1500), puis affichez c4.solde

4. Un affichage lisible : __str__

Par défaut, print(objet) donne une ligne peu lisible. La méthode spéciale __str__ décide ce qui s’affiche : elle doit renvoyer une chaîne, et print l’utilise automatiquement. On ajoute aussi une méthode description qui renvoie un libellé — elle nous servira plus loin.

class CompteBancaire:
    def __init__(self, titulaire, solde):
        self.titulaire = titulaire
        self.solde = solde
    def depot(self, montant):
        self.solde = self.solde + montant
    def retrait(self, montant):
        if montant <= self.solde:
            self.solde = self.solde - montant
        else:
            print("Solde insuffisant")
    def peut_retirer(self, montant):
        return montant <= self.solde
    def __str__(self):
        return f"Compte de {self.titulaire} : {self.solde} F"
    def description(self):
        return "compte courant"

c1 = CompteBancaire("Astou", 5000)
print(c1)
c1.depot(2000)
print(c1)
print(c1.description())
Compte de Astou : 5000 F
Compte de Astou : 7000 F
compte courant

À vous. Créez un compte pour "Bineta" avec 9000 et affichez-le directement avec print.

# À vous : creez un compte pour "Bineta" avec 9000, puis affichez-le avec print(...)

5. L’héritage : spécialiser sans réécrire

L’héritage crée une classe à partir d’une autre : elle récupère tout (attributs et méthodes) et peut ajouter ce qui lui est propre. CompteEpargne(CompteBancaire) se lit « un compte épargne est un compte bancaire, en plus spécialisé ». super().__init__(...) appelle le constructeur du parent pour ne pas réécrire l’installation des données communes.

class CompteEpargne(CompteBancaire):
    def __init__(self, titulaire, solde, taux):
        super().__init__(titulaire, solde)
        self.taux = taux
    def ajouter_interets(self):
        self.solde = self.solde + self.solde * self.taux

e1 = CompteEpargne("Bineta", 10000, 0.05)
print(e1)
e1.depot(5000)
e1.ajouter_interets()
print(e1.solde)
Compte de Bineta : 10000 F
15750.0

e1 profite de tout CompteBancaire : print(e1) utilise le __str__ hérité, et e1.depot(5000) la méthode depot héritée. ajouter_interets et l’attribut taux lui sont propres. Après le dépôt (solde 15000), les intérêts à 5 % portent le solde à 15750.

e1.retrait(2000)
print(e1.solde)
13750.0

À vous. Créez un compte épargne e2 pour "Modou" avec 20000 et un taux de 0.03, appliquez ajouter_interets(), puis affichez son solde.

# À vous : creez e2 = CompteEpargne("Modou", 20000, 0.03), faites e2.ajouter_interets(), puis affichez e2.solde

6. Le polymorphisme : un même appel, des comportements différents

Redéfinir dans la classe fille une méthode du parent s’appelle surcharger. Le polymorphisme en découle : un même appel produit un résultat différent selon le type réel de l’objet. On surcharge description dans CompteEpargne, puis on parcourt une liste mélangée.

class CompteEpargne(CompteBancaire):
    def __init__(self, titulaire, solde, taux):
        super().__init__(titulaire, solde)
        self.taux = taux
    def ajouter_interets(self):
        self.solde = self.solde + self.solde * self.taux
    def description(self):
        return f"compte epargne a {self.taux}"

comptes = [CompteBancaire("Astou", 5000), CompteEpargne("Bineta", 10000, 0.05)]
for c in comptes:
    print(c.titulaire, ":", c.description())
Astou : compte courant
Bineta : compte epargne a 0.05

La boucle appelle exactement le même c.description() à chaque tour, mais Python choisit la version qui correspond au type réel de l’objet : « compte courant » pour l’un, « compte epargne » pour l’autre. C’est le polymorphisme.

À vous. Construisez une liste mes_comptes contenant un CompteBancaire et un CompteEpargne de votre choix, puis affichez la description() de chacun avec une boucle for.

# À vous : creez mes_comptes = [ ... , ... ], puis parcourez-la et affichez c.description() pour chaque c

Le polymorphisme fonctionne aussi à travers une fonction : elle accepte n’importe quel compte et appelle description(), sans se soucier du type exact.

def presenter(compte):
    print("Il s'agit d'un", compte.description())

presenter(comptes[0])
presenter(comptes[1])
Il s'agit d'un compte courant
Il s'agit d'un compte epargne a 0.05

À vous. Écrivez une fonction resume(compte) qui affiche le titulaire suivi de la description du compte, puis appelez-la sur comptes[0] et comptes[1].

# À vous : definissez resume(compte) qui affiche compte.titulaire et compte.description(), puis appelez-la

7. Pièges fréquents

  • Oublier self : toute méthode prend self en premier paramètre, et l’on écrit self.attribut pour accéder aux données de l’objet. L’oubli provoque une erreur.
  • Confondre la classe et l’instance : CompteBancaire est le plan ; c1 = CompteBancaire(...) est un objet concret. Les méthodes s’appellent sur l’instance.
  • Appeler une méthode sans parenthèses : c1.description seul ne fait rien (il désigne la méthode) ; il faut écrire c1.description() pour l’exécuter.

Récapitulatif

  • Classe : le plan d’un objet (class). Instance : un objet concret créé à partir du plan.
  • __init__(self, ...) : le constructeur ; installe les attributs (self.x = ...).
  • self : l’objet courant ; toute méthode le reçoit en premier paramètre.
  • Méthode : une fonction définie dans la classe ; s’appelle avec objet.methode(...) ; peut modifier l’objet ou renvoyer une valeur.
  • __str__ : renvoie la chaîne affichée par print(objet).
  • Héritage : class Fille(Parent) récupère tout et ajoute ; super().__init__(...) réutilise le constructeur parent.
  • Polymorphisme : une méthode surchargée donne, pour un même appel, un comportement propre à chaque classe.

Prochaine séance — S6 : NumPy et le calcul sur des tableaux, premier outil de la science des données.

Retour au sommet