Académique Documents
Professionnel Documents
Culture Documents
KINDU - RD.CONGO
Cyrille KESIKU
cyrillekesiku2@gmail.com
Novembre 2023
1
Chapitre 1. Introduction à la Programmation Orientée Objet
2
1.1. Concepts fondamentaux de la POO .
3
1.1.1. Complexité des problèmes
4
Le logiciel développé doit présenter les caractéristiques suivantes :
5
- Utilisation : la capacité d'un logiciel à être utilisé entièrement ou
en partie pour de nouvelles applications. Le concept à garder est
de "ne pas réinventer la roue!" Programmez moins pour
programmer mieux.
6
1.1.2. Classe
7
1.1.3 Objet
8
Il s'agit d'un élément tangible qui contient les caractéristiques de
sa classe, comme une maison qui suit les plans déterminés
auparavant. On dit aussi que l’objet est une instance de la classe.
9
Chaque objet peut faire des actions qu’on appelle méthodes.
Alors un objet peut avoir une ou plusieurs méthodes.
Exemple :
10
1.1.4. Encapsulation
11
Il existe trois niveaux de visibilité :
12
1.1.5. Héritage des classes
13
Exemple :
Classe : Personne{
Nom
Prenom La classe Étudiant peut hériter de la
Sexe classe Personne ses attributs et ses
Date naissance méthodes pour éviter la réécriture du
} code.
Classe : Etudiant{
Nom
Prenom
Sexe
Date naissance
…
}
14
1.6. Le polymorphisme
15
1.2. Avantages de la POO dans le développement logiciel.
16
1.3. Python en tant que langage de programmation
orientée objet.
- Sa flexibilité élevée
- Un langage hybrid
- Intégration de plusieurs librairies facilitant la production logiciel
- et bien d’autres
17
1.4. Installation et configuration de l'environnement Python.
• VS-CODE et
• jupyter notebook
18
Chapitre 2 : Création de Classes et d'Objets
19
2.1. Création de classes.
Les noms des classes en Python commencent par convention par une
lettre en majuscule.
20
Voici une classe en python:
Python :
class personne:
pass
21
2.2. Instanciation d'objets.
Python:
nouvelle_personne = Personne()
22
On peut vérifier si nouvelle_personne est l’instance de classe
Personne.
Python:
print(isinstance(nouvelle_personne, Personne))
Ou
print(type(nouvelle_personne))
23
2.3. Attributs d’objet
Python:
import numpy as np
# ajout d'un attribut x avec comme valeur matrice 3x3 dans l'objet.
objet_matriciel.x = np.array([[2,7,8],[6,9,4],[1,2,4]])
24
Dans l’exemple precedent nous avons creer un objet dans lequel
nous avons ajouter un attribute.
Python:
print(objet_matriciel.x)
25
Exercice1 :
• Ajouter un attribut det_x avec comme valeur le déterminant de la matrice x dans
l’objet ‘’objet_mariciel’’
• Ajouter un deuxième attribut valeurs_propres avec comme valeur, les valeurs propres
de la x dans l’objet ‘’objet_mariciel’’
import numpy as np
objet_matriciel.det_x = np.linalg.det(objet_matriciel.x)
objet_matriciel.valeurs_p = np.linalg.eig(objet_matriciel.x)[0]
objet_matriciel.vecteur_p = np.linalg.eig(objet_matriciel.x)[1]
print(objet_matriciel.x,'\n')
print(objet_matriciel.det_x,'\n')
print(objet_matriciel.valeurs_p,'\n')
print(objet_matriciel.vecteur_p,'\n')
Un attribut peut être supprimée aussi en utilisant le mot del.
Par exemple:
Python
del objet_matriciel.det_x
print(objet_matriciel.det_x,'\n')
2.4. Méthodes d'objet.
class Calcul_matriciel:
Python:
# le constructeur
def __init__(self , x=0 , y=0):
self.x = x
self.y = y
Calcul_matriciel.determinant = np.linalg.det(x)
def calcul_produit_matrice(self , x , y):
return np.dot(self.x , self.y)
Python:
## Méthodes de classes
import numpy as np
class Calcul_matriciel:
determinant = 0
numero_objet = 0
# le constructeur
def __init__(self , x=0 , y=0):
self.x = x
self.y = y
Calcul_matriciel.determinant = np.linalg.det(x)
Calcul_matriciel.numero_objet += 1
Python :
@classmethod # Le décorateur
def affichiernumero(cls):
return cls.numero_objet
- Privée
- Publique
- Protégée
Il faut noter qu'il ne s'agit que d'une convention. Elle n'empêche pas les
autres programmeurs d'accéder aux attributs en utilisant la notation par
points, comme dans obj._attr.
# Méthode getter()
def get_x(self):
return self._x
# Méthode setter()
def set_x(self , value):
self._x = value
Python
Python :
Classe de base (classe mère ou parent) : Une classe qui définit les attributs et les méthodes de
base que d'autres classes vont hériter.
Classe dérivée (classe enfant ou sous-classe) : Une classe qui hérite des attributs et des
méthodes d'une classe de base. Elle peut également ajouter de nouveaux attributs et méthodes ou
modifier ceux hérités.
Héritage simple : Une classe enfant peut hériter des caractéristiques d'une seule classe de base.
Python ne prend pas en charge l'héritage multiple directement.
Méthode d'initialisation (__init__) : La méthode spéciale __init__ est utilisée pour initialiser les
attributs d'une classe. Lorsqu'une classe enfant hérite de la classe de base, elle peut appeler la
méthode __init__ de la classe parent pour initialiser ses propres attributs.
Méthode super() : La fonction super() permet d'appeler une méthode d'une classe parent à partir
de la classe enfant, ce qui est utile pour initialiser les attributs hérités.
Exemple héritage :
Créons une classe de base Equation avec des classes enfants pour
résoudre différentes équations mathématiques, notamment des équations
linéaires et quadratiques. Chacune des classes enfant héritera de la classe
de base Equation et implémentera sa propre méthode pour résoudre
l'équation
Python:
# Classe parent
class Equation:
def __init__(self, coefficients):
self.coefficients = coefficients
def resoudre(self):
pass
Première classe enfants : EquationLinaire
Python:
class EquationLineaire(Equation):
def resoudre(self):
if len(self.coefficients) != 2:
return "L'équation linéaire doit avoir exactement 2 coefficients."
a, b = self.coefficients
if a == 0:
if b == 0:
return "L'équation a une infinité de solutions."
else:
return "L'équation n'a pas de solution."
x = -b / a
return f" La solution de l'équation linéaire est x = {x}"
Deuxième classe enfants : EquationQuadratique
Python :
import math
class EquationQuadratique(Equation):
def resoudre(self):
if len(self.coefficients) != 3:
return "L'équation quadratique doit avoir exactement 3 coefficients."
a, b, c = self.coefficients
discriminant = b**2 - 4*a*c
if discriminant > 0:
x1 = (-b + math.sqrt(discriminant)) / (2*a)
x2 = (-b - math.sqrt(discriminant)) / (2*a)
return f"Les solutions de l'équation quadratique sont x1 = {x1} et x2 = {x2}"
elif discriminant == 0:
x = -b / (2*a)
return f"L'équation quadratique a une solution double : x = {x}"
else:
return "L'équation quadratique n'a pas de solution réelle."
# Utilisation des classes
coefficients_lineaire = [2, -4]
coefficients_quadratique = [1, -3, 2]
equation_lineaire = EquationLineaire(coefficients_lineaire)
equation_quadratique = EquationQuadratique(coefficients_quadratique)
print(equation_lineaire.resoudre())
print(equation_quadratique.resoudre())
Dans cet exemple, Equation est la classe de base avec une méthode resoudre,
qui est une méthode abstraite (elle ne fait rien). Les sous-classes
EquationLineaire, EquationQuadratique héritent de Equation et implémentent
leur propre logique pour résoudre des équations linéaires et
quadratiques respectivement.
3.2. Création de sous-classes et d'héritage multiple
class ClasseParent1:
# Attributs et méthodes de la classe parent 1
class ClasseParent2:
# Attributs et méthodes de la classe parent 2
Lorsque vous appelez une méthode dans une classe enfant, Python suit un
ordre de résolution des méthodes pour déterminer quelle classe parente
est consultée en premier.
# Classe parent1
class Adresse:
def __init__(self, rue, ville, code_postal):
self.rue = rue
self.ville = ville
self.code_postal = code_postal
def obtenir_adresse(self):
return f"{self.rue}, {self.ville}, {self.code_postal}"
# Classe parent2
class Contact:
def __init__(self, email, telephone):
self.email = email
self.telephone = telephone
def obtenir_contact(self):
return f"Email: {self.email}, Téléphone: {self.telephone}"
# Classe fille
class Personne(Adresse, Contact):
def __init__(self, nom, prenom, rue, ville, code_postal, email, telephone):
# Appel des constructeurs des classes parentes
Adresse.__init__(self, rue, ville, code_postal)
Contact.__init__(self, email, telephone)
self.nom = nom
self.prenom = prenom
def obtenir_info_personne(self):
return f"Nom: {self.nom}, Prénom: {self.prenom},
{self.obtenir_adresse()}, {self.obtenir_contact()}"
def description(self):
return f"Véhicule de marque {self.marque}, modèle {self.modele}"
def description(self):
return f"Voiture de marque {self.marque}, modèle {self.modele},
{self.nombre_portes} portes"
class Moto(Vehicule):
def __init__(self, marque, modele, cylindree):
super().__init__(marque, modele)
self.cylindree = cylindree
def description(self):
return f"Moto de marque {self.marque}, modèle {self.modele}, cylindrée
{self.cylindree}cc"
class ClasseEnfant(ClasseParent):
def __init__(self, nom, age):
super().__init__(nom) # Appel du constructeur de la classe parente
self.age = age
Appeler des méthodes de la classe parente :
class ClasseParent:
def methode_parent(self):
print("Méthode de la classe parent")
class ClasseEnfant(ClasseParent):
def methode_enfant(self):
super().methode_parent() # Appel de la méthode de la classe parente
print("Méthode de la classe enfant")
Appel de méthodes parentes avec des arguments :
class ClasseParent:
def methode_parent(self, argument):
print(f" Méthode de la classe parent avec {argument}")
class ClasseEnfant(ClasseParent):
def methode_enfant(self, argument):
# Appel de la méthode de la classe parente avec l'argument
super().methode_parent(argument)
print(f" Méthode de la classe enfant avec {argument}")
Exemple
class Personne:
def __init__(self, nom, age):
self.nom = nom
self.age = age
def afficher_info(self):
print(f"Nom: {self.nom}, Âge: {self.age}")
class Etudiant(Personne):
def __init__(self, nom, age, programme):
super().__init__(nom, age) # Appel du constructeur de la classe parente
self.programme = programme
def afficher_info(self):
super().afficher_info() # Appel de la méthode de la classe parente
print(f" Programme: {self.programme}")
# Création d'instances des classes
personne = Personne("Alice", 30)
etudiant = Etudiant("Bob", 25, "Informatique")
class Forme:
def aire(self):
pass
class Cercle(Forme):
def __init__(self, rayon):
self.rayon = rayon
def aire(self):
return math.pi * self.rayon ** 2
class Carre(Forme):
def __init__(self, cote):
self.cote = cote
def aire(self):
return self.cote ** 2
class Triangle(Forme):
def __init__(self, base, hauteur):
self.base = base
self.hauteur = hauteur
def aire(self):
return 0.5 * self.base * self.hauteur
# Utilisation du polymorphisme
cercle = Cercle(5)
carre = Carre(4)
triangle = Triangle(3, 6)
def calculer_aire(self):
return 3.14159 * self.rayon ** 2
class Carre(FormeGeometrique):
def __init__(self, cote):
self.cote = cote
def calculer_aire(self):
return self.cote ** 2
class Triangle(FormeGeometrique):
def __init__(self, base, hauteur):
self.base = base
self.hauteur = hauteur
def calculer_aire(self):
return 0.5 * self.base * self.hauteur
# Fonction qui calcule et affiche l'aire de n'importe quelle forme géométrique
def afficher_aire(forme):
aire = forme.calculer_aire()
print(f" L'aire de la forme géométrique est : {aire} unités carrées")
# Utilisation de l'interface
cercle = Cercle(5)
carre = Carre(4)
triangle = Triangle(3, 6)
1. Comment pourriez-vous utiliser l'héritage pour créer des classes spécifiques pour différentes catégories
de restaurants (par exemple, "Boulangerie", "Pizzeria", "Restaurant asiatique") tout en partageant
certaines fonctionnalités de base ?
2. Comment pourriez-vous mettre en œuvre le polymorphisme pour garantir que différentes
classes de restaurants partagent une méthode commune, par exemple, pour afficher les détails
du restaurant, tout en permettant à chaque classe de fournir sa propre implémentation si
nécessaire ?
3. Comment utiliseriez-vous l'héritage pour créer une classe de base pour les inspections des
restaurants (à partir des éléments de la liste "grades" dans le dictionnaire) tout en permettant
aux classes dérivées de représenter différentes inspections (par exemple, "InspectionQualite",
"InspectionSécurité") ?
5. Comment utiliseriez-vous l'héritage pour créer une classe de base pour les coordonnées (à partir
de l'élément "coord" dans le dictionnaire) et permettre à des sous-classes spécifiques de gérer
différents types de coordonnées (par exemple, "CoordonneesPoint", "CoordonneesGPS") tout en
partageant certaines méthodes de base pour afficher les coordonnées ?
Chapitre 4 : Gestion des Exceptions et des Erreurs
Les points clés de traitement des erreurs peut inclure plusieurs astuces.
Pour gérer les exceptions, on utilise des blocs try, except, else, et
finally
4.2.1. try :
Le bloc try est utilisé pour encapsuler le code qui peut potentiellement
générer une exception. C'est le bloc où on met le code qu’on souhaite
surveiller pour des erreurs. Si une exception est levée pendant l'exécution
de ce bloc, le contrôle est transféré au bloc except correspondant.
try:
# Code susceptible de provoquer une exception
except ExceptionType:
# Gérer l'exception ici
4.2.2. except :
Le bloc except est utilisé pour gérer une exception spécifique lorsque
elle est levée. Vous spécifiez le type d'exception à gérer après le mot-
clé except.
Le bloc else est exécuté si aucune exception n'est levée dans le bloc try.
Cela permet d'ajouter du code qui doit être exécuté en l'absence
d'erreurs.
try:
# Code susceptible de provoquer une exception
except ExceptionType:
# Gérer l'exception ici
else:
# Code à exécuter si aucune exception n'est levée
4.2.4. finally (optionnel) :
Le bloc finally est utilisé pour spécifier du code qui doit être exécuté
qu'une exception soit levée ou non.
C'est souvent utilisé pour des opérations de nettoyage, telles que la
fermeture de fichiers ou de connexions réseau.
try:
# Code susceptible de provoquer une exception
except ExceptionType:
# Gérer l'exception ici
else:
# Code à exécuter si aucune exception n'est levée
finally:
# Code à exécuter quoi qu'il arrive
Exemple
try:
# Tentative d'ouverture et de lecture d'un fichier texte
file = open("mon_fichier.txt", "r")
content = file.read()
except FileNotFoundError:
print("Le fichier n'a pas été trouvé.")
except IOError as e:
print(f" Erreur d'E/S : {e}")
else:
print("Lecture du fichier réussie.")
print(f" Contenu du fichier : {content}")
finally:
try:
file.close() # Tentative de fermeture du fichier, même en cas d'erreur
except NameError:
pass # Si le fichier n'a pas pu être ouvert, file n'existe pas
Exemple :
class SoldeInsuffisantException(Exception):
def __init__(self, solde, montant_retrait):
self.solde = solde
self.montant_retrait = montant_retrait
message = f"Solde insuffisant : Le solde actuel est de {solde}, mais
{montant_retrait} a été demandé."
super().__init__(message)
class CompteBancaire:
def __init__(self, solde):
self.solde = solde
try:
compte.retirer(1500)
except SoldeInsuffisantException as e:
print(e)
try:
compte.retirer(500)
except SoldeInsuffisantException as e:
print(e)
class MonObjet:
def __init__(self):
self.attribute = 42
obj = MonObjet()
try:
valeur = obj.attribut_inexistant
except AttributeError:
print("L'attribut n'existe pas.")
4.4.4. Gestion des erreurs personnalisées :
class MonExceptionPersonnalisee(Exception):
def __init__(self, message):
super().__init__(message)
class MonObjet:
def operation_risquee(self):
if erreur:
raise MonExceptionPersonnalisee("Une erreur s'est produite.")
4.4.5. Utilisation des blocs try, except, else, et finally :
Vous pouvez utiliser ces blocs pour gérer les erreurs liées à la POO
de manière similaire à la gestion des erreurs dans le code traditionnel. Par
exemple, dans le constructeur, vous pouvez placer du code
potentiellement problématique dans un bloc try et gérer les erreurs dans
le bloc except.
class MonObjet:
def __init__(self, valeur):
try:
if valeur < 0:
raise ValueError("La valeur doit être positive.")
self.valeur = valeur
except ValueError as e:
print(f"Erreur : {e}")
4.5. Utilisation du mécanisme de gestion des erreurs pour
améliorer la robustesse des programmes.
Utilisez le bloc finally pour garantir que les ressources telles que
les fichiers sont correctement fermées, quel que soit le résultat. Cela
évite les fuites de ressources
4.5.6. Gérer les erreurs de manière appropriée :
Décidez de la manière dont vous souhaitez gérer les erreurs. Vous pouvez
les ignorer, afficher un message d'erreur, enregistrer les erreurs dans un
journal, ou prendre des mesures spécifiques pour corriger le problème.
Ces principes ont été formulés par Robert C. Martin et sont couramment
utilisés pour guider la conception de logiciels orientés objet.
5.1.1. Principe de Responsabilité Unique (Single Responsibility
Principle - SRP)
def lire_fichier(self):
with open(self.nom_fichier, 'r') as fichier:
contenu = fichier.read()
return contenu
def analyser_contenu(self):
contenu = self.lire_fichier()
lignes = contenu.split('\n')
nombre_lignes = len(lignes)
print(f" Le fichier contient {nombre_lignes} lignes.")
# Utilisation de la classe
gestionnaire = GestionnaireFichier('exemple.txt')
gestionnaire.analyser_contenu()
Dans exemple précédant, la classe GestionnaireFichier a deux
responsabilités : lire un fichier et analyser son contenu.
Par exemple, nous pourrions avoir une classe LecteurFichier pour lire le
fichier et une classe AnalyseurContenu pour analyser son contenu.
Cela rendrait le code plus propre, plus modulaire et plus facile à maintenir,
en suivant le principe de base du SRP.
class LecteurFichier:
def __init__(self, nom_fichier):
self.nom_fichier = nom_fichier
def lire_fichier(self):
with open(self.nom_fichier, 'r') as fichier:
contenu = fichier.read()
return contenu
class AnalyseurContenu:
def __init__(self, contenu):
self.contenu = contenu
def analyser_nombre_lignes(self):
lignes = self.contenu.split('\n')
nombre_lignes = len(lignes)
return nombre_lignes
# Utilisation des classes
lecteur = LecteurFichier('exemple.txt')
contenu = lecteur.lire_fichier()
analyseur = AnalyseurContenu(contenu)
nombre_lignes = analyseur.analyser_nombre_lignes()
Le principe OCP stipule que les entités logicielles (comme les classes, les
modules ou les fonctions) doivent être ouvertes à l'extension mais fermées
à la modification.
Fermeture : Le code source existant, une fois testé et validé, ne doit pas
être modifié, car cela peut introduire des bogues ou des régressions.
class Rectangle(Forme):
def __init__(self, largeur, hauteur):
self.largeur = largeur
self.hauteur = hauteur
def aire(self):
return self.largeur * self.hauteur
class Cercle(Forme):
def __init__(self, rayon):
self.rayon = rayon
def aire(self):
return 3.14159 * self.rayon * self.rayon
Dans l’exemple précèdent, la classe Forme est ouverte à l'extension, car
nous pouvons ajouter de nouvelles sous-classes (comme Rectangle et
Cercle) pour étendre son comportement sans modifier la classe Forme
elle-même.
Ce principe stipule que les objets de sous-classes doivent pouvoir être utilisés
de manière interchangeable avec des objets de la classe de base sans altérer la
cohérence du programme.
En d'autres termes, si une classe S est une sous-classe d'une classe T, alors un
objet de la classe T peut être remplacé par un objet de la classe S sans que cela
entraîne des erreurs ou une violation des contrats (comportements) établis par
la classe de base.
Les principaux objectifs du principe LSP sont les suivants :
class Aigle(Oiseau):
def voler(self):
return "L'aigle vole très haut."
class Pingouin(Oiseau):
def voler(self):
return "Le pingouin ne peut pas voler, il nage."
Supposons que nous ayons une interface Appareil qui représente des
appareils électroniques et qui contient trois méthodes : allumer, éteindre
et réparer.
from abc import ABC, abstractmethod
class Appareil(ABC):
@abstractmethod
def allumer(self):
pass
@abstractmethod
def éteindre(self):
pass
@abstractmethod
def réparer(self):
pass
Cependant, toutes les classes d'appareils électroniques ne nécessitent pas toutes ces
méthodes. Par exemple, une lampe ne peut pas être réparée. En suivant le Principe ISP,
nous devrions séparer ces méthodes en interfaces distinctes pour les rendre plus
spécifiques aux besoins des classes qui les implémentent :
from abc import ABC, abstractmethod
class Allumable(ABC):
@abstractmethod
def allumer(self):
pass
@abstractmethod
def éteindre(self):
pass
class Réparable(ABC):
@abstractmethod
def réparer(self):
pass
Maintenant, une classe Lampe peut implémenter l'interface Allumable
et une classe Ordinateur peut implémenter les interfaces Allumable et
Réparable, ce qui respecte le Principe ISP en offrant des interfaces
spécifiques aux besoins de chaque classe.
class LecteurDisque:
def lire_fichier(self, chemin):
# Logique de lecture du fichier depuis le disque
pass
class LecteurFichier:
def __init__(self, lecteur_disque):
self.lecteur_disque = lecteur_disque
class LecteurAbstrait(ABC):
@abstractmethod
def lire_fichier(self, chemin):
pass
class LecteurDisque(LecteurAbstrait):
def lire_fichier(self, chemin):
# Logique de lecture du fichier depuis le disque
pass
class LecteurFichier:
def __init__(self, lecteur):
self.lecteur = lecteur
# Installation de pymongo
pip install pymongo
pip install — upgrade httpcore
# Connexion à MongoDB
import pymongo
# Connexion à la base de données
client = pymongo.MongoClient("mongodb://localhost:27017/")
db = client["nom_de_base_de_donnees"]
On doit s’assurer que MongoDB est en cours d'exécution sur la machine locale sur le
port par défaut (27017). Modifiez l'URL de connexion en conséquence si nécessaire.
# Création de la collection
collection = db["nom_de_votre_collection"]
#Insertion de données
# Exemple d'insertion d'un document
data_to_insert = {
"nom": "John Doe",
"age": 30,
"ville": "Paris"
}
result = collection.insert_one(data_to_insert)
print(f"ID du document inséré : {result.inserted_id}")
#Recherche de données
data_to_insert = {
"nom": "John Doe",
"age": 30,
"ville": "Paris"
}
result = collection.insert_one(data_to_insert)
print(f "ID du document inséré : {result.inserted_id}")
# Recherche de données