Prérequis : cours sur la POO
À l’issue de ce chapitre, vous saurez :
Note : les notions d’héritage, de polymorphisme et de généricité présentées dans cette page sont hors programme de terminale NSI. Elles sont proposées à titre de culture informatique et d’approfondissement.
Python se détourne parfois du paradigme objet en autorisant beaucoup de contournements du modèle.
La notion d’objet en programmation prolonge celle de type abstrait algébrique. Un objet regroupe données et traitements mais d’autres idées sont généralement aussi associées à la POO (programmation orientée objet) sans lui être exclusives :
Une classe est un modèle de création d’instances ; elle peut aussi être un modèle de création de classes (type abstrait, interface). Une classe abstraite spécifie seulement les opérations permises sur les données de l’objet, sans définir comment elles fonctionnent.
Les objets « discutent » entre eux ; la présentation usuelle de la POO parle même d’envois de messages. C’est cette « discussion » qui forme le programme qui s’exécute.
Persistance : les données d’un objet continuent d’exister même lorsque l’objet n’est pas « actif », qu’aucune de ses méthodes n’est en cours d’exécution.
Encapsulation : Les données d’un objet lui appartiennent et peuvent (dans la plupart des langages) être protégées des interventions extérieures à l’objet. L’encapsulation permet de garantir le fonctionnement d’un objet dans tout environnement qui l’utilise. Les opérations permises sur les données de l’objet forment l’interface de l’objet.
Héritage : une classe peut déclarer vérifier une ou plusieurs interface(s) - ce qui organise le code et facilite la maintenance - ou disposer automatiquement des méthodes d’autre(s) classe(s). Ainsi des méthodes déjà définies peuvent être réutilisées, sans réécrire plusieurs fois les mêmes codes.
Polymorphisme : une classe peut manipuler des données dont le type n’est pas précisé dans la définition de la classe, par exemple une classe Pile peut accepter des éléments de type Réel ou Caractères ou d’un quelconque autre type.
Généricité : grâce en particulier à l’héritage et au polymorphisme, l’étude d’un problème conduit à une solution abstraite qui facilite la résolution de problèmes similaires.
La classe détermine les attributs et les méthodes applicables à un objet.
La classe list a bien une méthode append mais pas la classe tuple.
objet = [1, 2, 3]
print(type(objet))
objet.append(4)
print(objet)
objet = (1, 2, 3)
print(type(objet))
objet.append(4)
print(objet)
On peut ajouter dynamiquement des attributs et des méthodes à une classe existante ou à des objets déjà créés. On peut même créer une classe pour créer des objets vides et les remplir après.
En Python, un attribut (ou une méthode) dont le nom commence par un tiret bas, comme _solde dans la classe CompteBancaire
du cours précédent, est considéré par convention comme interne, donc normalement non utilisable de l’extérieur
de l’objet. Rien n’empêche pourtant d’y accéder. Avec deux tirets bas (__solde), Python renomme l’attribut en
_CompteBancaire__solde : l’écriture compte.__solde échoue alors hors de la classe (erreur AttributeError), mais
compte._CompteBancaire__solde reste accessible. L’accès est gêné, pas interdit.
Ce qui suit, montre ce qu’il est possible de faire avec les objets en Python.
Attention : tout ce qui est possible n’est pas nécessairement conseillé ! Ce qui suit est destiné à permettre de comprendre le fonctionnement des objets en Python. Dans la pratique, il est plutôt conseillé d’utiliser les objets de Python conformément au paradigme objet qui consiste à utiliser les classes pour créer des objets similaires.
class ObjetVide:
"""Cette classe définit des objets sans attribut ni méthode """
pass
On crée deux instances d’objets vides :
un_objet = ObjetVide()
autre_objet = ObjetVide()
Chaque objet définit un espace de nommage distinct. Dans cet espace on peut définir des variables et leur affecter des valeurs. On crée ainsi des attributs d’instance.
un_objet.nom = "Objet 1"
un_objet.description = "C'est mon premier objet Python !"
Les espaces de nommage de deux objets (même s’ils sont instances de la même classe) sont indépendants.
autre_objet.nom = "Objet 2"
autre_objet.description = "C'est le deuxième."
autre_objet.prix = 10000
On vérifie en affichant les attributs des deux objets.
print(f"{un_objet.nom} : {un_objet.description}")
print(f"{autre_objet.nom} : {autre_objet.description} Valeur : {autre_objet.prix}")
On peut ajouter une méthode à la classe ObjetVide.
def affiche(self):
print(self.nom)
ObjetVide.affiche = affiche
Cette méthode devient immédiatement utilisable pour tous les objets de la classe.
un_objet.affiche()
La méthode peut aussi être appelée par son nom dans sa classe. Il faut alors utiliser comme paramètre le nom de l’objet.
ObjetVide.affiche(autre_objet)
On peut aussi définir des attributs de classe.
ObjetVide.numero = 12
Tous les objets de la classe en profitent, mais doivent partager cet attribut.
print(un_objet.numero)
ObjetVide.numero += 1
print(un_objet.numero)
print(autre_objet.numero)
Les objets peuvent aussi le redéfinir.
autre_objet.numero += 1
print(un_objet.numero)
print(autre_objet.numero)
Attention : le caractère dynamique rend rapidement incompréhensible ce mélange d’attributs de classe et d’attributs d’instance.
Il convient donc de limiter les attributs de classe au cas où cela est strictement nécessaire.
Il est aussi plus prudent de ne définir des attributs d’instance qu’à l’intérieur d’une méthode d’initialisation d’un objet.
Complément hors programme. L’héritage n’est pas exigible au baccalauréat ; il est présenté ici parce qu’il est omniprésent dans les bibliothèques Python.
L’héritage est largement utilisé dans les classes prédéfinies dans les bibliothèques Python. Quand une classe B hérite d’une classe A, toutes les méthodes de la classe A sont disponibles dans la classe B qui peut éventuellement en redéfinir certaines.
Cette architecture permet de définir des comportements généraux dans des classes générales, puis de redéfinir une partie des traitements dans des classes plus spécialisées.
On propose une classe Polynome puis une sous-classe Affine qui
hérite des méthodes de la classe Polynome.
class Polynome:
"""Construit un polynome par la liste de ses coefficients donnés par degré décroissant"""
def __init__(self,coefs):
self.coefs = coefs
def eval(self,x):
px = 0
for c in (self.coefs):
px = px * x + c
return (px)
def __add__(p,q):
""" pour l'addition, on a besoin de coefficients ordonnés par degrés croissant """
pr = list(reversed(p.coefs))
qr = list(reversed(q.coefs))
if len(pr) <= len(qr):
cr = [pr[i] + qr[i] for i in range(len(pr))] + qr[len(pr):]
else:
cr = [pr[i] + qr[i] for i in range(len(qr))] + pr[len(qr):]
return(Polynome(list(reversed(cr))))
def __repr__(self):
termes = []
d = len(self.coefs)
for i in range(d):
c = self.coefs[i]
p = d - i - 1
if c != 0:
if p == 0:
termes.append(str(c))
elif p == 1:
termes.append(str(c) + "x")
else:
termes.append(str(c) + "x^" + str(p))
return " + ".join(termes).replace("+ -", "- ")
On peut alors construire des polynômes en donnant la liste de leurs coefficients par degré décroissant, puis les additionner, les afficher et les évaluer en un point. La méthode __repr__ écrit le polynôme terme par terme, sous une forme lisible.
p = Polynome([1,0,-1])
q = Polynome([7, 3, -4, 1])
print(p)
print(q)
print(p+q)
print((p+q).eval(5))
On peut maintenant définir la sous-classe Affine, en ne redéfinissant que l’initialisation et en profitant de toutes les méthodes héritées. Une classe peut
hériter d’une ou plusieurs classes - notées entre parenthèses au moment de la création de la classe.
class Affine(Polynome):
def __init__(self,a,b):
self.coefs = [a,b]
On peut alors utiliser la classe Affine avec toutes ses méthodes héritées de la classe Polynome.
f = Affine(2,4)
g = Affine(-1, 6)
print(f)
print(g)
print(f+g)
f.eval(12)
Activité : Consulter la documentation de la classe standard tuple et les méthodes disponibles sur python.org
. Observer la classe Couple et la manière dont la
méthode d’addition est modifiée.
class Couple(tuple):
def __add__ (t1, t2):
return(t1[0]+t2[0], t1[1]+t2[1])
c = Couple((1,2))
d = Couple((3,4))
c + d
int, list, tuple…) sont eux-mêmes des classes.__init__._attribut, renommage des noms en __attribut) n’est pas strictement appliquée en Python : il reste possible d’accéder aux attributs internes.