0% ont trouvé ce document utile (0 vote)
1 vues25 pages

Cours Python Poo

Transféré par

Rakotondravelo
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
1 vues25 pages

Cours Python Poo

Transféré par

Rakotondravelo
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

■ PYTHON

Programmation Orientée Objet

Cours Complet — du débutant à l'expert

Niveau Débutant → Avancé

Chapitres 10 chapitres complets

Contenu Théorie + Exemples + Exercices

Version Python 3.10+

Ce cours vous guide pas à pas dans la maîtrise de la Programmation Orientée Objet avec
Python. Chaque chapitre combine théorie claire, exemples pratiques et exercices pour
consolider vos acquis.
Chapitre — Table des Matières

Chapitre 1 — Introduction à la POO


› Paradigmes de programmation
› Concepts fondamentaux
› Avantages de la POO

Chapitre 2 — Classes et Objets


› Définir une classe
› Attributs et méthodes
› Instanciation

Chapitre 3 — Constructeurs et Attributs


› __init__
› Attributs d'instance vs de classe
› Valeurs par défaut

Chapitre 4 — Encapsulation
› Principe d'encapsulation
› Attributs privés
› Getters & Setters
› Properties

Chapitre 5 — Héritage
› Héritage simple
› Héritage multiple
› super()
› MRO

Chapitre 6 — Polymorphisme
› Duck typing
› Surcharge d'opérateurs
› Méthodes spéciales

Chapitre 7 — Abstraction
› Classes abstraites (ABC)
› Interfaces en Python
› Méthodes abstraites

Chapitre 8 — Méthodes et Attributs Spéciaux


› Méthodes dunder
› Représentation
› Comparaison
› Conteneurs

Chapitre 9 — Patrons de Conception (Design Patterns)


› Singleton
› Factory
› Observer
› Decorator

Chapitre 10 — Projet Pratique Complet


› Système de gestion
› Application des concepts
› Bonnes pratiques
Chapitre 1 — Introduction à la POO

■ Qu'est-ce que la Programmation Orientée Objet ?

La Programmation Orientée Objet (POO) est un paradigme de programmation qui organise le code
autour d'objets plutôt qu'autour de fonctions et de procédures. Un objet est une entité qui regroupe des
données (attributs) et des comportements (méthodes).

■ Les 4 paradigmes principaux

Paradigme Description Exemple

Procédural Séquence d'instructions C, Pascal

Fonctionnel Fonctions sans état Haskell, Erlang

Orienté Objet Objets avec état Python, Java, C++

Déclaratif Décrire le résultat SQL, HTML

■ Les 4 piliers de la POO

Encapsulation Héritage Polymorphisme Abstraction

Regrouper données et Une classe peut hériter Un même message peut Simplifier la complexité
méthodes, cacher les des attributs/méthodes produire des en exposant uniquement
détails internes. d'une autre. comportements l'essentiel.
différents.

■ Premier exemple : classe vs procédural

◆ Approche procédurale
# Approche procédurale

nom = 'Alice'

age = 30

salaire = 3000

def afficher_employe(nom, age, salaire):

print(f'Employé : {nom}, {age} ans, {salaire}€/mois')

afficher_employe(nom, age, salaire)


◆ Approche orientée objet
# Approche orientée objet

class Employe:

def __init__(self, nom, age, salaire):

[Link] = nom

[Link] = age

[Link] = salaire

def afficher(self):

print(f'Employé : {[Link]}, {[Link]} ans, {[Link]}€/mois')

alice = Employe('Alice', 30, 3000)

[Link]() # Employé : Alice, 30 ans, 3000€/mois

■ Note : La POO permet de regrouper les données et les comportements liés dans une même entité logique.
Chapitre 2 — Classes et Objets

■ Définir une classe

Une classe est un modèle (blueprint) qui décrit la structure et le comportement d'un type d'objet. Les
objets sont des instances concrètes de cette classe.
class NomClasse:

# Corps de la classe

attribut_de_classe = valeur

def methode(self):

pass

■ Exemple complet : classe Voiture

class Voiture:

# Attribut de classe (partagé par toutes les instances)

nombre_de_roues = 4

def __init__(self, marque, modele, annee):

# Attributs d'instance

[Link] = marque

[Link] = modele

[Link] = annee

[Link] = 0

def accelerer(self, increment):

[Link] += increment

print(f'{[Link]} accélère à {[Link]} km/h')

def freiner(self, decrement):

[Link] = max(0, [Link] - decrement)

def afficher_infos(self):

print(f'{[Link]} {[Link]} ({[Link]})')

print(f'Vitesse actuelle : {[Link]} km/h')

# Instanciation

ma_voiture = Voiture('Renault', 'Clio', 2022)

ma_voiture.accelerer(50) # Renault accélère à 50 km/h


ma_voiture.afficher_infos()

print(Voiture.nombre_de_roues) # 4 — attribut de classe

■ Objets et identité

Chaque objet créé est une entité distincte en mémoire. Deux objets issus de la même classe sont
indépendants.
v1 = Voiture('Peugeot', '208', 2021)

v2 = Voiture('Toyota', 'Yaris', 2023)

[Link](30)

print([Link]) # 0 — v2 n'est pas affecté par v1

print(v1 is v2) # False — objets distincts

print(id(v1), id(v2)) # adresses mémoire différentes

■ Note : self fait toujours référence à l'objet courant. C'est la convention universelle en Python.
Chapitre 3 — Constructeurs et Attributs

■ Le constructeur __init__

Le constructeur __init__ est une méthode spéciale appelée automatiquement lors de la création d'un
objet. Il initialise les attributs de l'instance.
class Personne:

def __init__(self, nom, age, ville='Paris'): # valeur par défaut

[Link] = nom

[Link] = age

[Link] = ville

p1 = Personne('Alice', 30) # ville = 'Paris' par défaut

p2 = Personne('Bob', 25, 'Lyon') # ville = 'Lyon'

print([Link]) # Paris

print([Link]) # Lyon

■ Attributs d'instance vs attributs de classe

class Compteur:

total = 0 # Attribut de CLASSE — partagé

def __init__(self, nom):

[Link] = nom # Attribut d'INSTANCE — propre à chaque objet

[Link] += 1

@classmethod

def get_total(cls):

return [Link]

c1 = Compteur('A')

c2 = Compteur('B')

c3 = Compteur('C')

print(Compteur.get_total()) # 3

■ Méthodes statiques et de classe

class Utilitaire:

_instances = 0
def __init__(self):

Utilitaire._instances += 1

@staticmethod

def convertir_celsius_fahrenheit(c):

# Pas besoin de self ou cls

return c * 9/5 + 32

@classmethod

def nombre_instances(cls):

return cls._instances

print(Utilitaire.convertir_celsius_fahrenheit(100)) # 212.0

u1 = Utilitaire()

u2 = Utilitaire()

print(Utilitaire.nombre_instances()) # 2

■ Note : @staticmethod n'a pas accès à la classe ni à l'instance. @classmethod reçoit cls (la classe) comme
premier argument.
Chapitre 4 — Encapsulation

■ Principe d'encapsulation

L'encapsulation consiste à restreindre l'accès direct aux attributs internes d'un objet pour protéger son
état et garantir la cohérence des données. Elle distingue l'interface publique de l'implémentation interne.

■ Niveaux d'accès en Python

Convention Syntaxe Signification

Public attribut Accessible partout

Protégé _attribut Convention : usage interne (non strict)

Privé __attribut Name mangling — difficile d'accéder hors classe

■ Exemple : attribut privé avec getters/setters

class CompteBancaire:

def __init__(self, titulaire, solde_initial=0):

[Link] = titulaire

self.__solde = solde_initial # attribut privé

def deposer(self, montant):

if montant > 0:

self.__solde += montant

print(f'+{montant}€ déposés')

def retirer(self, montant):

if 0 < montant <= self.__solde:

self.__solde -= montant

print(f'-{montant}€ retirés')

else:

print('Solde insuffisant ou montant invalide')

def get_solde(self):

return self.__solde

compte = CompteBancaire('Alice', 500)


[Link](200)

[Link](100)

print(compte.get_solde()) # 600

# print(compte.__solde) # AttributeError !

■ Properties : la façon Pythonique

class Temperature:

def __init__(self, celsius=0):

self._celsius = celsius

@property

def celsius(self):

return self._celsius

@[Link]

def celsius(self, valeur):

if valeur < -273.15:

raise ValueError('Température impossible (< zéro absolu)')

self._celsius = valeur

@property

def fahrenheit(self):

return self._celsius * 9/5 + 32

t = Temperature(25)

print([Link]) # 77.0

[Link] = 100

print([Link]) # 212.0

[Link] = -300 # ValueError

■ Note : Les @property permettent d'accéder aux attributs comme s'ils étaient publics tout en contrôlant les
accès.
Chapitre 5 — Héritage

■ Principe de l'héritage

L'héritage permet à une classe enfant (sous-classe) d'hériter des attributs et méthodes d'une classe
parente (super-classe). Cela favorise la réutilisation du code et la hiérarchie logique entre les objets.
class Animal:

def __init__(self, nom, age):

[Link] = nom

[Link] = age

def respirer(self):

print(f'{[Link]} respire...')

def se_presenter(self):

print(f'Je suis {[Link]}, {[Link]} ans')

class Chien(Animal): # Chien hérite de Animal

def __init__(self, nom, age, race):

super().__init__(nom, age) # appel constructeur parent

[Link] = race

def aboyer(self):

print(f'{[Link]} : Woof !')

rex = Chien('Rex', 5, 'Labrador')

rex.se_presenter() # méthode héritée

[Link]() # méthode héritée

[Link]() # méthode propre à Chien

■ Surcharge de méthodes (Override)

class Chat(Animal):

def __init__(self, nom, age, interieur=True):

super().__init__(nom, age)

[Link] = interieur

def se_presenter(self): # SURCHARGE

super().se_presenter() # appel de la version parente

print(f'Je suis un chat {'d\'intérieur' if [Link] else 'd\'extérieur'}')


def ronronner(self):

print(f'{[Link]} : Purr...')

minou = Chat('Minou', 3)

minou.se_presenter()

# Je suis Minou, 3 ans


# Je suis un chat d'intérieur

■ Héritage multiple

class Nageur:

def nager(self):

print('Je nage')

class Coureur:

def courir(self):

print('Je cours')

class Triathlète(Nageur, Coureur):

def s_entrainer(self):

[Link]()

[Link]()

t = Triathlète()

t.s_entrainer()

■ MRO — Ordre de Résolution des Méthodes


# Python utilise l'algorithme C3 pour résoudre l'héritage multiple

print(Triathlète.__mro__)

# (<class 'Triathlète'>, <class 'Nageur'>, <class 'Coureur'>, <class 'object'>)

# isinstance et issubclass

print(isinstance(rex, Animal)) # True

print(isinstance(rex, Chien)) # True

print(issubclass(Chien, Animal)) # True

■ Note : Toute classe Python hérite implicitement de object, la classe racine universelle.
Chapitre 6 — Polymorphisme

■ Qu'est-ce que le polymorphisme ?

Le polymorphisme (du grec : plusieurs formes) permet à des objets de types différents de répondre de
manière appropriée à un même message (appel de méthode). En Python, il est naturel grâce au duck
typing.

■ Duck Typing

"Si ça marche comme un canard et que ça cancane comme un canard, c'est un canard."
class Chien:

def parler(self):

return 'Woof!'

class Chat:

def parler(self):

return 'Miaou!'

class Perroquet:

def parler(self):

return 'Coucou !'

animaux = [Chien(), Chat(), Perroquet()]

for animal in animaux:

print([Link]()) # chaque objet répond à sa façon

# Woof!
# Miaou!
# Coucou !

■ Surcharge d'opérateurs

Python permet de redéfinir le comportement des opérateurs (+, -, *, ==…) via les méthodes spéciales.
class Vecteur:

def __init__(self, x, y):

self.x = x

self.y = y

def __add__(self, autre): # surcharge de +

return Vecteur(self.x + autre.x, self.y + autre.y)


def __mul__(self, scalaire): # surcharge de *

return Vecteur(self.x * scalaire, self.y * scalaire)

def __eq__(self, autre): # surcharge de ==

return self.x == autre.x and self.y == autre.y

def __repr__(self):

return f'Vecteur({self.x}, {self.y})'

v1 = Vecteur(1, 2)

v2 = Vecteur(3, 4)

print(v1 + v2) # Vecteur(4, 6)

print(v1 * 3) # Vecteur(3, 6)

print(v1 == v2) # False

■ Note : La surcharge d'opérateurs rend vos objets plus intuitifs et expressifs à utiliser.
Chapitre 7 — Abstraction

■ Concept d'abstraction

L'abstraction consiste à définir une interface commune sans spécifier l'implémentation. En Python, on
utilise le module abc (Abstract Base Classes) pour créer des classes abstraites qui imposent un contrat
aux sous-classes.

■ Classes abstraites avec ABC

from abc import ABC, abstractmethod

class Forme(ABC): # Classe abstraite

@abstractmethod

def aire(self) -> float:

pass

@abstractmethod

def perimetre(self) -> float:

pass

def description(self):

# Méthode concrète utilisable par toutes les formes

print(f'Aire = {[Link]():.2f}, Périmètre = {[Link]():.2f}')

# form = Forme() # TypeError : on ne peut pas instancier une classe abstraite

class Cercle(Forme):

def __init__(self, rayon):

[Link] = rayon

def aire(self):

import math

return [Link] * [Link] ** 2

def perimetre(self):

import math

return 2 * [Link] * [Link]

class Rectangle(Forme):

def __init__(self, largeur, hauteur):


[Link] = largeur

[Link] = hauteur

def aire(self):

return [Link] * [Link]

def perimetre(self):

return 2 * ([Link] + [Link])

formes = [Cercle(5), Rectangle(4, 6)]

for f in formes:

[Link]()

# Aire = 78.54, Périmètre = 31.42


# Aire = 24.00, Périmètre = 20.00

■ Protocoles (Python 3.8+)

from typing import Protocol

class Dessinable(Protocol):

def dessiner(self) -> None: ...

class Carré:

def dessiner(self):

print('■ Dessin d\'un carré')

class Triangle:

def dessiner(self):

print('■ Dessin d\'un triangle')

def afficher(forme: Dessinable):

[Link]()

afficher(Carré()) # ■ Dessin d'un carré

afficher(Triangle()) # ■ Dessin d'un triangle

■ Note : Les Protocols permettent le duck typing statiquement vérifié sans héritage explicite.
Chapitre 8 — Méthodes et Attributs Spéciaux
(Dunder)

■ Qu'est-ce que les méthodes dunder ?

Les méthodes dunder (double underscore) comme __init__, __str__, __len__… permettent à vos
classes de s'intégrer naturellement avec la syntaxe Python et ses fonctions intégrées.

Méthode Déclenchée par Usage

__init__ ClassName() Constructeur

__str__ str(obj), print(obj) Représentation lisible

__repr__ repr(obj) Représentation de débogage

__len__ len(obj) Longueur

__getitem__ obj[clé] Accès par index/clé

__setitem__ obj[clé] = val Affectation par index

__contains__ x in obj Test d'appartenance

__iter__ for x in obj Itération

__eq__ obj1 == obj2 Égalité

__lt__ obj1 < obj2 Inférieur à

__add__ obj1 + obj2 Addition

__enter__/__exit__ with obj as x Context manager

■ Exemple : classe Liste personnalisée

class MaListe:

def __init__(self, *args):

self._data = list(args)

def __len__(self):

return len(self._data)

def __getitem__(self, index):

return self._data[index]

def __setitem__(self, index, valeur):

self._data[index] = valeur
def __contains__(self, item):

return item in self._data

def __iter__(self):

return iter(self._data)

def __str__(self):

return f'MaListe({self._data})'

def __repr__(self):

return f'MaListe(*{self._data!r})'

ml = MaListe(10, 20, 30, 40)

print(len(ml)) # 4

print(ml[1]) # 20

print(30 in ml) # True

for v in ml: print(v, end=' ') # 10 20 30 40

print(str(ml)) # MaListe([10, 20, 30, 40])

■ Note : Implémenter les méthodes dunder rend vos objets 'pythoniques' et compatibles avec les idiomes du
langage.
Chapitre 9 — Patrons de Conception (Design
Patterns)

■ Introduction aux Design Patterns

Les patrons de conception sont des solutions éprouvées à des problèmes récurrents de conception
logicielle. Ils se classent en trois familles : créationnels, structurels et comportementaux.

◆ 1. Singleton — une seule instance

class Singleton:

_instance = None

def __new__(cls, *args, **kwargs):

if cls._instance is None:

cls._instance = super().__new__(cls)

return cls._instance

def __init__(self, valeur=None):

if not hasattr(self, 'initialise'):

[Link] = valeur

[Link] = True

s1 = Singleton(42)

s2 = Singleton(99)

print(s1 is s2) # True — même objet !

print([Link]) # 42

◆ 2. Factory — créer des objets sans spécifier leur classe

class Animal:

def parler(self): pass

class Chien(Animal):

def parler(self): return 'Woof!'

class Chat(Animal):

def parler(self): return 'Miaou!'

class AnimalFactory:

_registry = {'chien': Chien, 'chat': Chat}


@classmethod

def creer(cls, type_animal: str) -> Animal:

klass = cls._registry.get(type_animal.lower())

if klass is None:

raise ValueError(f'Inconnu : {type_animal}')

return klass()

a = [Link]('chien')

print([Link]()) # Woof!

◆ 3. Observer — notification d'événements

class Evenement:

def __init__(self):

self._abonnes = []

def abonner(self, callback):

self._abonnes.append(callback)

def emettre(self, *args, **kwargs):

for cb in self._abonnes:

cb(*args, **kwargs)

class Capteur:

def __init__(self):

self.on_mesure = Evenement()

self._valeur = 0

def mesurer(self, valeur):

self._valeur = valeur

self.on_mesure.emettre(valeur)

capteur = Capteur()

capteur.on_mesure.abonner(lambda v: print(f'Alerte ! valeur = {v}'))

capteur.on_mesure.abonner(lambda v: print(f'Log : {v}'))

[Link](42)

# Alerte ! valeur = 42
# Log : 42
Chapitre 10 — Projet Pratique Complet

■ Système de gestion d'une bibliothèque

Ce projet applique tous les concepts vus : classes, héritage, encapsulation, polymorphisme, méthodes
spéciales et design patterns.

◆ Modèle de données

from abc import ABC, abstractmethod

from datetime import date, timedelta

# ■■ Document (classe abstraite) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

class Document(ABC):

def __init__(self, titre, auteur, annee):

[Link] = titre

[Link] = auteur

[Link] = annee

self._emprunte = False

@property

def emprunte(self):

return self._emprunte

@abstractmethod

def type_document(self) -> str:

pass

def __str__(self):

statut = 'emprunté' if self._emprunte else 'disponible'

return f'[{self.type_document()}] {[Link]} ({[Link]}, {[Link]}) —


{statut}'

def __repr__(self):

return f'{self.__class__.__name__}({[Link]!r})'

# ■■ Sous-classes ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

class Livre(Document):

def __init__(self, titre, auteur, annee, isbn):

super().__init__(titre, auteur, annee)

[Link] = isbn
def type_document(self):

return 'Livre'

class Revue(Document):

def __init__(self, titre, auteur, annee, numero):

super().__init__(titre, auteur, annee)

[Link] = numero

def type_document(self):

return 'Revue'

# ■■ Emprunt ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

class Emprunt:

DUREE_DEFAUT = 14 # jours

def __init__(self, document, adherent):

[Link] = document

[Link] = adherent

self.date_emprunt = [Link]()

self.date_retour = self.date_emprunt + timedelta(days=self.DUREE_DEFAUT)

document._emprunte = True

def retourner(self):

[Link]._emprunte = False

print(f'{[Link]} retourné par {[Link]}')

def est_en_retard(self):

return [Link]() > self.date_retour

def __str__(self):

retard = ' ■ EN RETARD' if self.est_en_retard() else ''

return (f'{[Link]} — {[Link]} | '

f'retour prévu : {self.date_retour}{retard}')

# ■■ Bibliothèque (Façade + gestion) ■■■■■■■■■■■■■■■■■■■■■■■■■■■■

class Bibliotheque:

def __init__(self, nom):

[Link] = nom

self._catalogue: list[Document] = []

self._emprunts: list[Emprunt] = []

def ajouter(self, document: Document):

self._catalogue.append(document)

print(f'Ajout : {document}')
def rechercher(self, mot_cle: str) -> list[Document]:

mc = mot_cle.lower()

return [d for d in self._catalogue

if mc in [Link]() or mc in [Link]()]

def emprunter(self, titre: str, adherent: str):

for d in self._catalogue:

if [Link]() == [Link]():

if [Link]:

print(f'{titre} est déjà emprunté')

return None

emp = Emprunt(d, adherent)

self._emprunts.append(emp)

print(f'Emprunt : {emp}')

return emp

print(f'Document non trouvé : {titre}')

return None

def rapport(self):

print(f'\n=== {[Link]} — {len(self._catalogue)} document(s) ===')

for d in self._catalogue:

print(' ', d)

# ■■ Démonstration ■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■■

bib = Bibliotheque('Médiathèque Centrale')

[Link](Livre('Le Petit Prince', 'Saint-Exupéry', 1943, '978-2-07-040850-4'))

[Link](Revue('Science & Vie', 'Collectif', 2024, 1298))

[Link](Livre('Clean Code', 'Robert C. Martin', 2008, '978-0-13-235088-4'))

[Link]('Clean Code', 'Alice')

[Link]('Clean Code', 'Bob') # déjà emprunté

resultats = [Link]('prince')

print('Résultats :', resultats)

[Link]()

■ Bonnes pratiques POO

• Principe de responsabilité unique (SRP) : une classe = une responsabilité.


• Préférez la composition à l'héritage quand c'est possible.
• Utilisez les @property plutôt que les getters/setters explicites.
• Documentez vos classes avec des docstrings.
• Utilisez les type hints pour clarifier les signatures.
• Écrivez des tests unitaires pour chaque classe.
• Respectez le principe SOLID pour un code maintenable.

■ Félicitations !

Vous avez parcouru l'ensemble du cours Python POO. Vous maîtrisez maintenant les classes, l'héritage,
l'encapsulation, le polymorphisme, l'abstraction, les méthodes spéciales et les design patterns. Pratiquez
régulièrement en construisant vos propres projets !

Vous aimerez peut-être aussi