L3 MIASHS, Algorithme et Programmation 3, année 2024
Rappel sur les grands paradigmes de programmation
En AP2, nous avons vu que la programmation se décomposait en paradigmes, c’est-à-dire en différentes méthodologies.
La programmation impérative est la plus répandue. Il s’agit simplement de décrire la liste d’instructions avec des opérateurs de contrôle simples (boucle, conditions, fonctions).
La programmation fonctionnelle propose d’éviter certaines constructions classiques qui entraînent de nombreux effets indésirables. Il s’agit de composer des fonctions les unes avec les autres avec des opérateurs fonctionnels (map, fold, filter, etc.). On a vu des exemples en AP2.
Nous allons voir un nouveau paradigme : la programmation orientée objet. Son objectif est de structurer le code en entités complexes qui reflètent une certaine réalité : des objets.
La programmation orientée objet est très utile pour le travail collaboratif et la segmentation du code. Chaque Objet représente un morceau du programme avec son environnement bien isolé. Pour cette raison, les langages fortement orientés objets sont très utilisés pour les gros projets, couplés avec des méthodologies de gestion de projet.
Les contraintes liées à la programmation orientée objet sont plus ou moins poussées selon les langages de programmation. Dans le cas de Python, on y trouve les concepts de base (objet et héritages). Les concepts plus avancés nécessitent des langages fortement typés (polymorphisme, interface abstraite, implémentation concrète).
Dans ce cours, on se limitera aux concepts présents dans l’écosystème Python. Pour en savoir plus, il faut étudier des langages différents (C++, Java par exemple).
Introduction à l’objet en Python : les classes.
Une classe en Python est un bout de code qui décrit le comportement d’un objet. Une instance d’une classe est un objet Python manipulable qui suit le comportement imposé par la classe. Il possède sa mémoire interne et des opérations propres.
- Une fonction qui appartient à un objet s’appelle une méthode. Son premier argument est toujours une instance.
- Une variable qui appartient à un objet s’appelle un champ (ou field en anglais).
Un exemple trop simple
class UneClasseInutile:
pass
print(UneClasseInutile)
<class 'UneClasseInutile'>
Ici la classe UneClasseInutile
ne sert à rien, il s’agit
d’une classe vide sans méthodes ni champ (ni rien).
On peut l’instancier en un objet (qui ne sert à rien non plus !) en appelant la classe comme une fonction.
= UneClasseInutile()
A print(A)
<UneClasseInutile object at 0x7fb82c464c10>
Une première méthode
Une méthode est une fonction, qui a en premier argument une variable
(qu’on appelle usuellement self
) et qui représente
l’instance de l’objet.
class EncoreUne:
def une_methode(self, a):
print("je suis une méthode!!")
self.x = a # self représente une instance quelconque.
= EncoreUne()
a 10)
a.une_methode(print(a.x)
je suis une méthode!!
10
On remarque qu’une méthode peut également avoir d’autres arguments
(ou aucun) mais dans tous les cas elle doit avoir self
en
premier argument. Ici, a.x
est une variable locale
à l’instance a
.
5)
a.une_methode(print(a.x)
je suis une méthode!!
5
On peut stocker des variables locales à un objet de manière dynamique :
= 123
a.y print(a.y)
123
ou supprimer un champ :
del a.y
print(a.y)
Traceback (most recent call last):
File "/var/www/cha/check_py_md", line 81, in repl
exec(code, env)
File "<string>", line 2, in <module>
AttributeError: 'EncoreUne' object has no attribute 'y'
Dans l’absolu, une classe peut avoir plein de méthodes et plein de variables locales.
Des méthodes particulières
Certaines méthodes sont particulières et permettent de donner un
comportement à la classe bien spécifique. Elles ont en général des noms
particuliers entourés de __
.
- Initialisations : Quand on crée une instance à partir d’une classe, une fonction standard ou une fonction d’initialisation est appelée. Par exemple :
class AvecUnInit:
def __init__(self, x):
print("Création d'une nouvelle instance!!!")
self.x = x # le champ x est initialisé à la valeur passée en argument.
= AvecUnInit(42)
A print(A.x)
Création d'une nouvelle instance!!!
42
- Surcharge d’opérateurs : des opérateurs de la syntaxe python ou des fonctions standards de Python peuvent être appliqués sur des instances de classes à condition que la bonne méthode soit présente dans la classe. Par exemple, on peut surcharger l’addition pour lui faire faire des choses étranges.
class AdditionBarycentrique:
def __init__(self, x):
self.x = x
def __add__(self, other):
return AdditionBarycentrique((self.x+other.x)/2)
= AdditionBarycentrique(0)
A = AdditionBarycentrique(10)
B print((A+B).x)
5.0
On peut également changer la manière dont les instances sont
affichées quand on appelle print
ou str
.
class AdditionBarycentrique:
def __init__(self, x):
self.x = x
def __add__(self, other):
return AdditionBarycentrique((self.x+other.x)/2)
def __repr__(self):
return "B:"+str(self.x)
= AdditionBarycentrique(0)
A = AdditionBarycentrique(10)
B print(A, B, A+B)
B:0 B:10 B:5.0
- La liste des opérations surchargeables est grande, il y a beaucoup de marge de manoeuvre pour créer des objets informatiques agréables à manipuler pour l’utilisateur. C’est une des raisons du succès de Python. C’est aussi un de ses dangers.
On verra dans le cours suivant les notions d’héritage et de ducktyping.
Propriété, getter, setter
Il arrive que certaines valeurs d’un objet soient dépendantes de son
état interne. Il est alors important que ce champ lui soit réservé. Pour
cela, on peut utiliser une propriété qui va retourner un champ dont la
valeur est calculée par une fonction. On utilise le décorateur
@property
.
import math
class rectangle:
def __init__(self, largeur, longueur):
self.largeur = largeur
self.longueur = longueur
@property
def surface(self):
return math.sqrt(self.largeur**2 + self.longueur**2)
= rectangle(10, 20)
R print(R.surface) # Ce n'est pas une fonction, il n'y a pas les parenthèses !
22.360679774997898
Si on essaye de faire une assignation, Python n’est pas d’accord :
= 123 R.surface
Traceback (most recent call last):
File "/var/www/cha/check_py_md", line 81, in repl
exec(code, env)
File "<string>", line 1, in <module>
AttributeError: property 'surface' of 'rectangle' object has no setter
Ici, il est possible de modifier les valeurs des champs
largeur
et longueur
.
= 12
R.largeur print(R.surface) # la valeur est toujours correcte
23.323807579381203
On peut empêcher une valeur de changer en lui attribuant une propriété.
class rectangle:
def __init__(self, largeur, longueur):
self._largeur = largeur
self._longueur = longueur
@property
def surface(self):
return math.sqrt(self.largeur**2 + self.longueur**2)
@property
def largeur(self):
return self._largeur
@property
def longueur(self):
return self._longueur
= rectangle(10, 20)
R print(R.longueur, R.largeur, R.surface)
20 10 22.360679774997898
Mais par contre, on ne peut plus modifier les champs :
= 21 R.longueur
Traceback (most recent call last):
File "/var/www/cha/check_py_md", line 81, in repl
exec(code, env)
File "<string>", line 1, in <module>
AttributeError: property 'longueur' of 'rectangle' object has no setter
Méthode de classe
Il arrive qu’on veuille plusieurs constructeurs pour une classe. On
peut alors utiliser un décorateur @classmethod
pour
indiquer qu’une fonction définie dans la classe n’est pas une méthode
mais une fonction qui ne dépend que de la classe qui l’appelle.
Son premier argument est toujours la classe qui appelle la fonction (on verra pourquoi avec l’héritage !).
class rectangle:
def __init__(self, largeur, longueur):
self._largeur = largeur
self._longueur = longueur
@property
def surface(self):
return math.sqrt(self.largeur**2 + self.longueur**2)
@property
def largeur(self):
return self._largeur
@property
def longueur(self):
return self._longueur
@classmethod
def depuis_coordonnées(cls, x1, x2, y1, y2):
return cls(x2-x1, y2-y1)
Compiled the: mar. 26 nov. 2024 11:43:59 CET