Aller au contenu

Licence Creative Commons
Ce cours est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International.

Structurer avec des classes (Bac 🎯)⚓︎

programme

Sources et crédits pour ce cours

Pour préparer ce cours, j'ai utilisé :

🔖 Synthèse de ce qu'il faut retenir pour le bac

Un problème⚓︎

Problème

Dans un programme Python, on veut :

  • créer des vecteurs du plan à partir de leurs coordonnées
  • les manipuler à l'aide d'opérations classiques : calculer la norme d'un vecteur, additionner deux vecteurs, multiplier un vecteur par un scalaire.

Paradigme procédural : une solution avec des fonctions⚓︎

Solution avec variables et fonctions

A partir des connaissances de première, on peut définir :

  • une structure de données : un vecteur est un tuple de deux entiers. On choisit le type tuple plutôt que list car les coordonnées d'un vecteur ne doivent pas changer normalement, on dit qu'un vecteur est une donnée immuable
  • des fonctions pour manipuler cette structure de données, chacune renvoie un nouveau vecteur résultat de l'opération souhaitée

###
def addition(v1, v2):bksl-nl x1 = v1[0]bksl-nl y1 = v1[1]bksl-nl x2 = v2[0]bksl-nl y2 = v2[1]bksl-nl return (x1 + x2, y1 + y2)bksl-nlbksl-nldef multpy-undscal(v, k):bksl-nl x = v[0]bksl-nl y = v[1]bksl-nl return (k py-str x, k py-str y)bksl-nlbksl-nlw1 = (10, -4) # définition d'un premier vecteurbksl-nlw2 = (-3, 6) # définition d'un second vecteurbksl-nlw3 = addition(w1, w2) # addition de deux vecteursbksl-nlw4 = multpy-undscal(w1, 3) # multiplication d'un vecteur par un scalairebksl-nlprint("Vecteur w1 : ", w1)bksl-nlprint("Vecteur w2 : ", w2)bksl-nlprint("Addition de w1 et w2 : ", w3)bksl-nlprint("Multiplication de w1 par un scalaire : ", w4)bksl-nl

Cette façon de programmer est tout à fait convenable pour un petit script mais imaginons que ce code s'inscrit dans un projet plus grand où l'on manipule des temps qu'on doit aussi ajouter :

###
def addition(t1, t2):bksl-nl h1, m1, s1 = t1bksl-nl h2, m2, s2 = t2bksl-nl s3 = (s1 + s2) % 60bksl-nl m3 = (m1 + m2 + (s1 + s2) // 60) % 60bksl-nl h3 = h1 + h2 + (m1 + m2 + (s1 + s2) // 60) // 60bksl-nl return (h3, m3, s3)bksl-nlbksl-nlt1 = (1, 59, 45) # 1 heure 59 minutes 45 secondesbksl-nlt2 = (2, 0, 20) # 2 heures 0 minutes 20 secondesbksl-nlt3 = addition(t1 ,t2)bksl-nlprint("Temps t1 : ", t1)bksl-nlprint("Temps t2 : ", t2)bksl-nlprint("Addition de t1 et t2 : ", t3)bksl-nl

Plusieurs problèmes se posent lorsqu'on manipule plusieurs structures de données dans un gros programme. On peut citer entre autres :

  • Le problème du cloisonnement des espaces de nommage : dans l'exemple on voit qu'il faudrait distinguer les noms des fonctions d'addition sinon la dernière déclarée va écraser la précédente. Une solution1 serait d'ajouter des suffixes : addition_temps ou addition_vecteur mais c'est lourd. On aimerait disposer pour un même programme d'une façon de définir un espace de nommage distinct regroupant variables et fonctions pour une même structure de données.
  • Le problème de l'organisation du code : pour que le code soit lisible il faudrait regrouper les définitions des structures de données avec les déclarations des fonctions qui les manipulent. On aimerait disposer d'une syntaxe Python pour organiser nos structures de données et définir nos propres types de données personnalisés comme les types list ou tuple de Python.

Paradigme Objet : une solution avec des classes⚓︎

Solution avec classe, attributs et méthode

Le paradigme de Programmation Orientée Objet (POO) permet de répondre de façon élégante aux problèmes de cloisonnement des espaces de nommage et d'organisation du code. Un paradigme est une façon de programmer. En POO on manipule des objets qui interagissent entre eux.

  • Un objet représente une structure données par un exemple un vecteur du plan
  • Deux objets qui représentent la même structure de données appartiennent à la même classe, par exemple la classe Vecteur
  • Un objet possède un certain nombre d'attributs qui le caractérisent : par un exemple un objet de la classe Vecteur possède deux attributs x et y pour chaque coordonnée
  • Les attributs d'un objet sont définis lors de sa création par le constructeur de sa classe
  • Un objet peut posséder aussi un certain nombre de méthodes qui permettent de le manipuler : par exemple une méthode addition permet d'additionner un vecteur à un autre.

On donne une traduction dans le paradigme de Programmation Orientée Objet (POO) de la solution précédente écrite dans le paradigme procédural utilisé jusqu'à présent dans nos programmes.

On peut établir l'analogie suivante :

Paradigme procédural Paradigme objet
Variable Attribut
Fonction Méthode

###
class Vecteur:bksl-nl """Classe de fabrication d'un vecteur du plan"""bksl-nl bksl-nl def py-undpy-undinitpy-undpy-und(self, x, y):bksl-nl """Constructeur de la classe Vecteur"""bksl-nl self.x = x # attribut xbksl-nl self.y = y # attribut y bksl-nl bksl-nl def addition(self, autre):bksl-nl """Méthode d'addition à un autre vecteur"""bksl-nl return Vecteur(self.x + autre.x, self.y + autre.y)bksl-nl bksl-nlclass Temps:bksl-nl """Classe de fabrication d'un temps"""bksl-nl bksl-nl def py-undpy-undinitpy-undpy-und(self, h, m, s):bksl-nl """Constructeur de la classe Temps"""bksl-nl self.h = hbksl-nl self.m = mbksl-nl self.s = sbksl-nl bksl-nl def addition(self, autre):bksl-nl """Méthode d'addition à un autre temps"""bksl-nl h1, m1, s1 = self.h, self.m, self.sbksl-nl h2, m2, s2 = autre.h, autre.m, autre.sbksl-nl s3 = (s1 + s2) % 60bksl-nl m3 = (m1 + m2 + (s1 + s2) // 60) % 60bksl-nl h3 = h1 + h2 + (m1 + m2 + (s1 + s2) // 60) // 60bksl-nl return Temps(h3, m3, s3)bksl-nlbksl-nlw1 = Vecteur(10, -4) # construction d'un premier vecteurbksl-nlw2 = Vecteur(-3, 6) # construction d'un second vecteurbksl-nlw3 = w1.addition(w2) # appel de méthode d'addition de w1 à w2bksl-nlprint("Affichage de w3 : ", w3)bksl-nlprint("Composante x de w3 :", w3.x, "Composante y de w3 : ", w3.y)bksl-nlbksl-nlt1 = Temps(1, 59, 45) # construction d'un premier tempsbksl-nlt2 = Temps(2, 0, 20) # construction d'un second tempsbksl-nlt3 = t1.addition(t2) # appel de méthode d'addition de t1 à t2bksl-nlprint("Affichage de t3 : ", t3)bksl-nlprint("Composante h de t3 :", t3.h, "Composante m de t3 : ", t3.m, "Composante s de t3 : ", t3.s)bksl-nlbksl-nl

Certains points techniques seront explicités plus loin, mais on peut déjà remarquer que :

  • le mot clef class permet de définir une classe.
  • un objet est créé avec la syntaxe Nom_classe(paramètres) par exemple w1 = Vecteur(10, -4) ou t1 = Temps(1, 59, 45)
  • une action sur un objet est appliquée à l'aide d'une méthode en utilisant la notation pointée objet.methode(paramètres), par exemple w3 = w1.addition(w2) ou t3 = t1.addition(t2).
  • dans notre exemple, on définit deux classes :
    • chacune contient un constructeur __init__ permettant d'initialiser les attributs d'un objet
    • l'objet créé est désigné par self
    • chaque classe contient une méthode addition permettant d'additionner un autre objet de même classe à l'objet courant désigné par self.
    • __init__ et addition sont des noms partagés par les deux classes Vecteur et Temps, chacune définit donc un espace de nommage distinct.

Un peu d'histoire⚓︎

Exercice 1

🎯 Histoire de l'informatique

Cet exercice est un QCM. Vous devez cocher la seule réponse correcte par question. Vous pouvez faire des recherches sur le Web pour répondre.

  1. Question 1 : Qui est considéré comme le père de la programmation orientée objet ?

    • Alan Turing
    • John McCarthy
    • Dennis Ritchie
    • Alan Kay
  2. Question 2 : Quel langage de programmation a introduit pour la première fois le concept de programmation orientée objet ?

    • C
    • Java
    • Smalltalk
    • Python
  3. Question 3 : Quelle décennie est celle du triomphe de la programmation orientée objet ?

    • 1960
    • 1970
    • 1980
    • 1990
  1. Question 1 : Qui est considéré comme le père de la programmation orientée objet ?

    • ❌ Alan Turing
    • ❌ John McCarthy
    • ❌ Dennis Ritchie
    • ✅ Alan Kay
  2. Question 2 : Quel langage de programmation a introduit pour la première fois le concept de programmation orientée objet ?

    • ❌ C
    • ❌ Java
    • ✅ Smalltalk
    • ❌ Python
  3. Question 3 : Quelle décennie est celle du triomphe de la programmation orientée objet ?

    • ❌ 1960
    • ❌ 1970
    • ❌ 1980
    • ✅ 1990

Des objets partout en Python⚓︎

Exercice 2

Vous avez déjà manipulé des objets

La syntaxe objet.methode(paramètres) a déjà été utilisée en Première avec les types de données list ou str de Python. Les données de type list sont en fait des objets de la classe list. Toutes les données de Python sont des objets; les types de base sont des classes.

🐍 Script Python
>>> type([])
<class 'list'>
>>> type('')
<class 'str'>
>>> type(0)
<class 'int'>
>>> type(0.0)
<class 'float'>
>>> type(True)
<class 'bool'>

Question 1

Dresser un tableau d'état de la variable ls ligne par ligne lors de l'exécution du programme ci-dessous :

🐍 Script Python
1
2
3
4
5
6
ls = []
ls.append(14)  
ls.append(10)
ls.insert(1, 7)
ls.pop()
ls.extend([-4, 8])
🐍 Script Python
1
2
3
4
5
6
ls = []     
ls.append(14)  # ls == [14]
ls.append(10)   # ls == [14, 10]
ls.insert(1, 7) # ls == [14, 7, 10]
ls.pop()        # ls == [14, 7]
ls.extend([-4, 8]) # ls == [14, , -4, 8]

On peut afficher la liste des méthodes disponibles pour un objet avec dir(objet).

🐍 Script Python
>>> type([])
<class 'list'>
>>> dir([])
['__add__', '__class__', '__class_getitem__', '__contains__', '__delattr__', '__delitem__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__gt__', '__hash__', '__iadd__', '__imul__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__reversed__', '__rmul__', '__setattr__', '__setitem__', '__sizeof__', '__str__', '__subclasshook__', 'append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']

Notez que même les fonctions comme dir ou len sont en fait équivalentes à des méthodes de l'objet, même si c'est un peu particulier pour les types prédéfinis de Python écrits en C (c'est une fonction écrite en C qui est appelée pour la fonction len plutôt que la méthode __len__).

🐍 Script Python
>>> a = [1, 4]
>>> len(a)
2
>>> a.__len__()
2

Question 2

Écrire une fonction de signature uncapitalize(chaine:str)->str qui prend en paramètre une chaîne de caractères et renvoie la chaîne avec les mêmes caractères tous en majuscules sauf le premier.

⚠️ : contrainte : ne pas utiliser de boucle, seulement les méthodes de la classe str

🐍 Script Python
>>> uncapitalize("explicit i better than implicit")
'eXPLICIT I BETTER THAN IMPLICIT'
🐍 Script Python
1
2
def uncapitalize(chaine):
    return chaine.capitalize().swapcase()

On peut afficher la liste des méthodes disponibles pour un objet de type str avec dir('').

🐍 Script Python
>>> dir('')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isascii', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'removeprefix', 'removesuffix', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

  1. Une autre serait de définir chaque structure de données dans des programmes différents. Python permet alors d'importer ces modules dans le programme client. Par exemple des fonctions randint différentes existent dans le module random et dans le module numpy, on peut les utiliser dans le même programme en préfixant du nom du module : random.randint et numpy.randint. Mais il est naturel d'avoir envie de regrouper dans un même module des structures de données qui traitent du même thème, par exemple les différents types de personnage d'un jeu.