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
cest un objet de typeCompteurc.incrémente()agit sur le compteur, comme une fonctionc.valeurstocke 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
valeurcréé dans le constructeur, modifié dans la méthodeincrémenteet à l'extérieur (c2.valeur) - la méthode
incrémente - l'attribut
valeura des valeurs différents dans les deux instancesc1etc2 selfpermet d'accéder à l'attributvaleurpropre à l'instance considéréeselfapparaî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)