La programmation orientée objet (POO)¶
Un paradigme de programmation très répandu (Python, Java, C++, etc.)
Aujourd'hui:
- des exemples introductifs pour votre intuition
- les notions de bases de la POO
- attribut
- méthode
- constructeur
- définir vos propres classes
- compléments pour aller plus loin: UML, méthodes magiques, espaces de noms,
À venir: l'articulation entre classes (agrégation, composition, héritage)
Cette présentation est à compléter par l'étude du site https://francoisbrucker.github.io/cours_informatique/cours/coder-et-d%C3%A9velopper/programmation-objet/#classes-objets.
Promenade parmi les objets¶
Variables vs. fonctions¶
Vous avez appris à manipuler deux types d'entités:
- des variables: pour stocker des données
- des fonctions: pour effectuer des actions
def calcule_aire_carré(longueur_côté):
return longueur_côté ** 2
a = 3.14
b = calcule_aire_carré(a)
print(a, b)
3.14 9.8596
Un objet regroupe données et actions dans une seule entité!
Un compteur¶
from compteur_mpci import Compteur
c = Compteur()
print(c)
print(type(c))
Compteur de valeur 0 <class 'compteur_mpci.Compteur'>
c.incrémente()
print(c)
Compteur de valeur 1
print(c.valeur)
c.valeur = 20
print(c)
1 Compteur de valeur 20
c
est un objet de typeCompteur
c.incrémente()
agit sur le compteur, comme une fonctionc.valeur
stocke la donnée utile au compteur- un objet est une entité pouvant stocker des données et effectuer des actions.
Une classe pour les chaînes¶
Quel est le type exact d'une chaîne de caractères?
s = "Bonjour!"
print(s)
print(type(s))
Bonjour! <class 'str'>
C'est une classe!
# Convertir une chaîne en majuscules...
s_upper = s.upper() # Une "fonction" de conversion?
print(s_upper)
BONJOUR!
Une classe pour les entiers¶
Quel est le type exact d'un entier?
a = 42
print(type(a))
a.to_bytes() # encore une sorte de "fonction" attachée à la variable?
<class 'int'>
b'*'
Encore une classe! Et une sorte de fonction!
Une classe pour les complexes¶
Saviez-vous qu'il y a une classe pour les complexes?
print(1j ** 2)
z = 2 - 1j *0.5
print(z)
print(type(z))
(-1+0j) (2-0.5j) <class 'complex'>
Un complexe se caractérise par sa partie réelle et sa partie imaginaire. Comment y accéder?
x = z.real
y = z.imag
print(x, y)
print(type(x), type(y))
2.0 -0.5 <class 'float'> <class 'float'>
Comment calculer le conjugué?
zc = z.conjugate()
print(zc, type(zc))
(2+0.5j) <class 'complex'>
Une classe pour les dates et les heures¶
Ça existe et c'est très pratique!
import datetime
mpci_day0 = datetime.datetime(day=3, month=9, year=2024, hour=9, minute=0, second=0)
print(type(mpci_day0))
print(mpci_day0)
<class 'datetime.datetime'> 2024-09-03 09:00:00
print(mpci_day0.day)
print(mpci_day0.month)
print(mpci_day0.year)
print(mpci_day0.hour, mpci_day0.minute, mpci_day0.second)
3 9 2024 9 0 0
now = datetime.datetime.today()
print(type(now))
print(now)
<class 'datetime.datetime'> 2025-03-19 10:49:34.921872
d = now.__sub__(mpci_day0) # Soustraire deux objets datetime
print(type(d))
print("Je suis en MPCI depuis", d.days, "jours et", d.seconds, "secondes.")
<class 'datetime.timedelta'> Je suis en MPCI depuis 197 jours et 6574 secondes.
d = now - mpci_day0 # Plus pratique avec l'opérateur -
print("Je suis en MPCI depuis", d.days, "jours et", d.seconds, "secondes.")
Je suis en MPCI depuis 197 jours et 6574 secondes.
Et les graphiques?¶
Encore des classes!
import matplotlib.pyplot as plt
fig, axes = plt.subplots(1,2, figsize=(8, 2.5))
axes[0].plot([-1, 1, 5], [0, 3, 2])
axes[0].set_xlabel('Temps t')
axes[0].set_ylabel('f(t)')
axes[0].set_title('Courbe de gauche')
axes[1].plot([x for x in range(-10, 10)])
axes[1].plot([x ** 2 - 50 for x in range(-10, 10)])
axes[1].set_axis_off()
axes[1].set_title('Courbes de droite')
print(type(fig))
print(type(axes), axes.shape)
print(type(axes[0]))
<class 'matplotlib.figure.Figure'> <class 'numpy.ndarray'> (2,) <class 'matplotlib.axes._axes.Axes'>
À retenir¶
- En Python, tout est objet!
- Un objet permet de regrouper des données et des actions dans une seule entité pour de nombreuses bonnes raisons:
- DRY
- compartimentation et modularité du code
- structuration du code
- lisibilité
Et maintenant¶
- Classe, objet: késako?
- Comment utiliser une classe existante?
- Comment créer une nouvelle classe?
- Tout savoir sur les composants des classes?
Notions de base¶
Classe, objet, instance¶
De façon très similaire à un type, une classe est un modèle pour entités ayant des caractéristiques communes.
Instancier une classe signifie créer une entité de cette classe.
Un objet est une instance d'une classe.
# Exemple
c = Compteur()
print(type(c))
<class 'compteur_mpci.Compteur'>
L'objet c
est une instance de la classe Compteur
.
Attribut (de l'objet!)¶
- concept proche de celui de variable
- stocke les données propres à l'objet dans l'espace de nom de la classe ou de l'objet
- accessible en lecture et écriture avec la syntaxe
nom_objet.nom_attribut
, sans parenthèses
c.valeur = 3
c.valeur += 1
print(c)
Compteur de valeur 4
Méthode¶
- concept proche de celui de fonction
- effectue des actions propres à la classe ou à l'objet
- accessible avec la syntaxe
nom_objet.nom_méthode(arguments)
, avec des parenthèses
c.incrémente()
print(c)
c.initialise()
print(c)
Compteur de valeur 5 Compteur de valeur 0
Créer une classe¶
Déclarer le nom de la classe¶
On utilise le mot clé class
et on crée un bloc
class Compteur:
pass
Complétons le code de cette classe!
Le constructeur: pour instancier la classe¶
On crée une méthode __init__(self, argument1, argument2, ...)
. Elle est appelée automatiquement quand on crée un objet à l'aide du nom de la classe.
class Compteur:
def __init__(self):
print("Création d'un compteur")
c = Compteur() # Création de l'objet
print(type(c))
print(c)
Création d'un compteur <class '__main__.Compteur'> <__main__.Compteur object at 0x1040d4d10>
Et la variable self
?
Attributs et méthodes¶
Il est assez naturel de créer et manipuler des attributs et des méthodes lors de la définition d'une classe.
La variable self
, premier argument de chaque méthode, désigne l'instance courante.
class Compteur:
def __init__(self):
self.valeur = 0
def incrémente(self):
self.valeur += 1
c1 = Compteur()
c2 = Compteur()
print(c1.valeur, c2.valeur)
c1.incrémente()
print(c1.valeur, c2.valeur)
c2.valeur = 3
print(c1.valeur, c2.valeur)
0 0 1 0 1 3
Vous remarquez:
- les méthodes créées avec le mot clé
def
(comme les fonctions), à l'intérieur de la classe - l'attribut
valeur
créé dans le constructeur, modifié dans la méthodeincrémente
et à l'extérieur (c2.valeur
) - la méthode
incrémente
- l'attribut
valeur
a des valeurs différents dans les deux instancesc1
etc2
self
permet d'accéder à l'attributvaleur
propre à l'instance considéréeself
apparaît dans la déclaration de la méthode (def incrémente(self)
) mais pas dans l'appel à la méthode (c1.incrémente()
)
self
apparaît dans la déclaration de la méthode (def incrémente(self)
) mais pas dans l'appel à la méthode (c1.incrémente()
)
Explication: c'est un raccourci bien utile pour la syntaxe avec argument Classe.methode(instance)
.
class Compteur:
def __init__(self):
self.valeur = 0
def incrémente(self):
self.valeur += 1
c = Compteur()
c.incrémente() # Appel sans argument self
print(c.valeur)
Compteur.incrémente(c) # Appel équivalent avec argument self
print(c.valeur)
1 2
Compléments d'objet¶
Très pratiques: les méthodes magiques/spéciales¶
- exemples:
__init__
,__str__
,__sub__
,__add__
- permettent de définir des comportements spéciaux: instanciation, conversion en chaîne de caractères, opérateurs mathématiques, etc.
- facile à reconnaitre par leurs noms qui commencent et finissent par deux
_
- on les découvrira petit à petit
class Compteur:
def __init__(self):
self.valeur = 0
def __str__(self):
return f'Compteur de valeur {self.valeur}'
c = Compteur()
print(c)
print(" -> " + str(c))
Compteur de valeur 0 -> Compteur de valeur 0
import datetime
mpci_day0 = datetime.date(day=3, month=9, year=2024)
now = datetime.date.today()
d = now.__sub__(mpci_day0) # Soustraire deux objets datetime
print("Je suis en MPCI depuis", d.days, "jours.")
d = now - mpci_day0 # C'est bien plus pratique comme ça
print("Je suis en MPCI depuis", d.days, "jours.")
Je suis en MPCI depuis 197 jours. Je suis en MPCI depuis 197 jours.
Vous utilisez déjà des méthodes spéciales sans le savoir!
x = 1 + 2
# au lieu de
y = (1).__add__(2)
print(x == y)
# au lieu de
print(x.__eq__(y))
True True
L'UML : vive les maquettes¶
- UML = Unified Modeling Language
- langage très vaste : on se contente de quelques éléments
- très utile et répandu pour décrire des classes
classDiagram class NomDeLaClasse{ attributs constructeur() méthodes() }
classDiagram class Compteur{ valeur : int Compteur() incrémente() initialise() __str__() str }
Améliorer l'exemple de la classe Compteur
¶
- Ajouter un pas pour incrémenter le compteur d'une valeur différente de 1
- Autoriser une valeur initiale différente de 0
- Comparer des compteurs avec
==
,<
, etc.
classDiagram class Compteur{ valeur : int pas : int Compteur(pas=1, valeur=0) incrémente() initialise() __str__() str __eq__(other) bool __lt__(other) bool }
class Compteur:
def __init__(self, pas=1, valeur=0):
self.valeur = valeur
self.pas = pas
def incrémente(self):
self.valeur = self.valeur + self.pas
def __str__(self):
return f'Compteur de valeur {self.valeur} et de pas {self.pas}'
def __eq__(self, other):
return self.valeur == other.valeur
def __lt__(self, other):
return self.valeur < other.valeur
c1 = Compteur(3)
c2 = Compteur(valeur=10)
c1.incrémente()
c2.incrémente()
c1.incrémente()
print(c1)
print(c2)
print(c1 < c2, c1 == c2)
Compteur de valeur 6 et de pas 3 Compteur de valeur 11 et de pas 1 True False
Autres exemples
classDiagram class Personne{ nom : str prénom : str date_naissance : datetime.date Personne(prénom, nom, date_naissance) calcule_age() int } class Compte{ titulaire : Personne solde : float Compte(titulaire) retire(montant) float dépose(montant) float }
classDiagram class Point{ x : float y : float Point(x, y) distance(other) float } class Polygone{ sommets : [Point] Polygone(liste_sommets) périmètre() float aire() float }
Espaces de nom¶
Plusieurs espaces de noms sont imbriqués dans un objet:
- l'espace de nom d'une méthode
- l'espace de nom de l'instance
- l'espace de nom de la classe
class Compteur:
pas = 1 # un attribut de classe dans l'espace de nom de la classe
def __init__(self, valeur=0):
# argument valeur dans l'espace de nom de la méthode __init__
self.valeur = valeur # attribut self.valeur dans l'espace de nom de l'instance
def incrémente(self):
self.valeur = self.valeur + self.pas
def __str__(self):
return f'Compteur de valeur {self.valeur} et de pas {self.pas}'
c = Compteur(2)
c.incrémente()
print(c.valeur, c.pas, Compteur.pas)
try:
print(Compteur.valeur) # Erreur!!
except AttributeError as e:
print(e)
3 1 1 type object 'Compteur' has no attribute 'valeur'
c1 = Compteur(valeur=3)
c2 = Compteur(valeur=-5)
print(c1)
print(c2)
Compteur.pas = 2 # modification de l'attribut de classe
print(c1)
print(c2)
Compteur de valeur 3 et de pas 1 Compteur de valeur -5 et de pas 1 Compteur de valeur 3 et de pas 2 Compteur de valeur -5 et de pas 2
Conclusion¶
- Vous avez les bases théoriques sur les classes:
- classes, objets, instances
- attributs, méthodes
- constructeur
self
- UML, méthodes magiques, espaces de noms
- Il faut maintenant pratiquer, pratiquer, pratiquer, de la découverte à l'automatisme
- Prochaines séances:
- TP pour pratiquer
- Être et Avoir, ou l'articulation entre classes (agrégation, composition, héritage)