Prérequis : fonctions en Python , notion de type de données
À l’issue de ce chapitre, vous saurez :
- définir une classe avec des attributs d’instance et des méthodes ;
- créer des objets et appeler leurs méthodes avec la notation pointée ;
- écrire un constructeur
__init__pour initialiser les attributs ; - utiliser les méthodes spéciales (
__repr__,__add__,__eq__, etc.) pour personnaliser le comportement des objets.
Définitions
Jusqu’ici, nos programmes manipulaient d’un côté des données (nombres, listes, dictionnaires) et de l’autre des fonctions qui les traitent. La programmation orientée objet réunit les deux : un objet contient à la fois ses données et les opérations qui s’y appliquent. Tout ce qui concerne une même sorte de données se trouve ainsi écrit au même endroit.
- Les données d’un objet sont ses attributs.
- Les fonctions qui agissent sur un objet sont ses méthodes.
- Une classe décrit une sorte d’objets : quels attributs ils possèdent, quelles méthodes on peut leur appliquer. Elle sert aussi à fabriquer ces objets ; chaque objet fabriqué s’appelle une instance de la classe.
Un premier exemple : la classe Point
Un logiciel de dessin manipule des points. Chaque point possède deux attributs, ses coordonnées x et y, et une méthode translater qui le déplace.
- Pour un point
p, on lit ses attributs avec la notation pointée :p.xetp.y. - On applique une méthode avec la même notation :
p.translater(dx, dy)déplacepdedxhorizontalement et dedyverticalement.
Une première classe en Python
Une classe se définit avec le mot-clé class, suivi de son nom. L’usage veut que ce nom commence par une majuscule : Point, CompteBancaire.
Les fonctions écrites à l’intérieur de la classe sont ses méthodes. Leur premier paramètre, nommé self, reçoit l’objet auquel on applique la méthode.
class Point:
"""Point du plan."""
def translater(self, dx, dy):
"""Déplace le point de dx horizontalement et de dy verticalement."""
self.x = self.x + dx
self.y = self.y + dy
Appeler la classe comme une fonction, Point(), fabrique un nouvel objet. Dans l’appel p.translater(1, 2), Python transmet p au paramètre self, puis 1 et 2 à dx et dy.
Remarque 1. self n’est pas un mot réservé de Python : c’est seulement le nom que tout le monde donne, par convention, au premier paramètre d’une méthode.
Ici, l’objet est créé vide : on lui donne ses coordonnées de l’extérieur avant de le déplacer.
p = Point()
p.x = 3
p.y = 7
p.translater(1, 2)
print(f"{p.x} {p.y}")
4 9
Chaque objet a ses propres valeurs de x et de y : ce sont des attributs d’instance.
Remarque 2. Un attribut naît au moment où on lui affecte une valeur : p.x = 3 crée l’attribut x de p, de même que self.x = ... dans une méthode le crée pour l’objet self.
Création et initialisation d’un objet en Python
Que se passe-t-il si l’on oublie de donner ses coordonnées à un point avant de le déplacer ?
>>> q = Point()
>>> q.translater(1, 2)
AttributeError: 'Point' object has no attribute 'x'
L’objet q vient d’être créé, il n’a encore aucun attribut : translater ne trouve pas self.x. Pour éviter cette erreur, chaque point doit recevoir ses coordonnées dès sa création.
C’est le rôle de la méthode spéciale __init__. Quand on écrit Point(12, 6), Python crée un objet vide, puis appelle aussitôt __init__ sur cet objet, avec les arguments 12 et 6 ; __init__ crée alors les attributs.
class Point:
"""Point du plan."""
def __init__(self, abscisse, ordonnee):
"""Crée les attributs x et y du point."""
self.x = abscisse
self.y = ordonnee
def translater(self, dx, dy):
"""Déplace le point de dx horizontalement et de dy verticalement."""
self.x = self.x + dx
self.y = self.y + dy
Un point ne peut plus être créé sans ses coordonnées : tous les points possèdent donc x et y, et translater s’applique sans erreur.
p = Point(12, 6)
p.translater(1, 2)
print(f"{p.x} {p.y}")
13 8
Activité. Créer une classe Temps avec des attributs d’instance h, min et s, une méthode d’initialisation et une méthode addition.
Vérifiez votre compréhension
- Quelle est la différence entre une classe et un objet ?
Réponse
Une classe est un modèle (un plan), un objet est une instance concrète de ce modèle. On peut créer autant d'objets qu'on veut à partir d'une même classe, chacun avec ses propres valeurs d'attributs. - Pourquoi la méthode
__init__est-elle importante ?Réponse
Un objet vient au monde sans attribut : une méthode commetranslater, qui litself.x, échouerait sur un point vide. Python appelle__init__dès la création, ce qui donne à chaque objet ses attributs avant toute autre opération. - Que représente
selfdans une méthode ?Réponse
selfest une référence vers l'objet sur lequel la méthode est appelée. Quand on écritp.translater(1, 2), Python passe automatiquementpcomme valeur deself.
Interface et encapsulation
L’interface d’une classe est l’ensemble des méthodes que l’utilisateur de la classe peut appeler, avec leur spécification (paramètres, valeur renvoyée, effet). C’est le « mode d’emploi » de la classe : pour utiliser un objet Point, il suffit de connaître Point(x, y) et translater(dx, dy), sans lire le code des méthodes. L’addition de deux points, ajoutée plus bas, viendra compléter cette interface.
L’encapsulation consiste à regrouper les données (attributs) et les traitements (méthodes) dans l’objet, et à n’accéder aux données qu’à travers les méthodes de l’interface. L’état interne de l’objet reste ainsi cohérent : on ne peut pas mettre un compte bancaire à découvert si le solde n’est modifié que par deposer et retirer, qui vérifient les règles.
class CompteBancaire:
"""Un compte dont le solde ne peut pas devenir négatif."""
def __init__(self, titulaire):
self.titulaire = titulaire
self._solde = 0 # attribut interne : ne pas y toucher directement
def deposer(self, montant):
assert montant > 0, "le montant doit être positif"
self._solde = self._solde + montant
def retirer(self, montant):
assert 0 < montant <= self._solde, "solde insuffisant"
self._solde = self._solde - montant
def solde(self):
return self._solde
Python ne garantit pas l’encapsulation : rien n’interdit d’écrire compte._solde = -1000. La convention est claire : un nom qui commence par _ fait partie de l’implémentation et non de l’interface, on ne l’utilise pas de l’extérieur. Séparer interface et implémentation permet de modifier l’intérieur d’une classe (représentation des données, algorithme) sans changer les programmes qui l’utilisent : c’est le principe que l’on retrouvera avec les structures de données
, dont l’interface (pile, file, arbre) ne dépend pas de l’implémentation choisie.
Classes et objets en Python
Les classes ne sont pas réservées aux programmes que nous écrivons : en Python, toutes les valeurs sont des objets. Une liste, une chaîne de caractères ou un dictionnaire sont des instances des classes list, str et dict, et L.append(4) est l’appel d’une méthode.
La fonction dir donne la liste des noms d’attributs et de méthodes d’un objet. Gardons ceux qui ne commencent pas par __ :
p = Point(12, 6)
print([nom for nom in dir(p) if not nom.startswith("__")])
['translater', 'x', 'y']
L = [1, 8, 7]
print([nom for nom in dir(L) if not nom.startswith("__")])
['append', 'clear', 'copy', 'count', 'extend', 'index', 'insert', 'pop', 'remove', 'reverse', 'sort']
On retrouve pour p les attributs et la méthode de la classe Point, et pour L les méthodes des listes. Sans le filtre, dir affiche aussi des dizaines de noms encadrés par deux tirets bas, comme __init__ : ce sont les méthodes spéciales.
Méthodes spéciales en Python
Une méthode spéciale n’est presque jamais appelée par son nom : Python l’appelle tout seul quand on utilise une fonction ou un opérateur du langage. En la définissant dans une classe, on choisit ce que fait cette fonction ou cet opérateur pour les objets de la classe.
| On écrit | Python appelle |
|---|---|
Point(12, 6) | __init__(self, ...), juste après la création de l’objet |
str(p) ou print(p) | __str__(self), qui renvoie la chaîne à afficher |
repr(p) | __repr__(self) ; str et print s’en servent aussi quand __str__ n’est pas définie |
p == q | __eq__(self, other) |
p + q | __add__(self, other) |
len(p) | __len__(self) |
La documentation de Python les décrit toutes.
Suite de l’exemple de la classe Point
Donnons aux points deux affichages, l’un pour print, l’autre pour repr, et une addition qui ajoute les coordonnées une à une :
class Point:
"""Point du plan."""
def __init__(self, abscisse, ordonnee):
"""Crée les attributs x et y du point."""
self.x = abscisse
self.y = ordonnee
def translater(self, dx, dy):
"""Déplace le point de dx horizontalement et de dy verticalement."""
self.x = self.x + dx
self.y = self.y + dy
def __str__(self):
"""Renvoie la chaîne affichée par print : (x, y)."""
return f"({self.x}, {self.y})"
def __repr__(self):
"""Renvoie l'instruction qui recrée le point : Point(x, y)."""
return f"Point({self.x}, {self.y})"
def __add__(self, other):
"""Renvoie le point dont les coordonnées sont les sommes des coordonnées."""
return Point(self.x + other.x, self.y + other.y)
def __eq__(self, other):
"""Renvoie True si les deux points ont les mêmes coordonnées."""
return self.x == other.x and self.y == other.y
p = Point(12, 6)
q = Point(3, 4)
print(p)
print(repr(p))
print(p + q)
print([p, q])
print(p == Point(12, 6))
(12, 6)
Point(12, 6)
(15, 10)
[Point(12, 6), Point(3, 4)]
True
print(p) appelle __str__, qui écrit le point comme en mathématiques ; repr(p) appelle __repr__, qui reproduit l’instruction permettant de recréer le point. L’expression p + q appelle p.__add__(q), qui fabrique un nouveau point ; print l’affiche ensuite grâce à __str__. Pour afficher une liste, Python appelle __repr__ sur chaque élément, et non __str__. Enfin, p == Point(12, 6) appelle p.__eq__(Point(12, 6)), qui compare les coordonnées : sans cette méthode, == vérifierait seulement si les deux points sont le même objet, et donnerait False.
Remarque 3. __str__ et __repr__ doivent renvoyer leur chaîne avec return, sans l’afficher : c’est print qui l’affiche ensuite. Avec print à la place de return, la méthode renvoie None, et print(p) s’arrête sur l’erreur TypeError: __str__ returned non-string (type NoneType).
- La POO regroupe données (attributs) et traitements (méthodes) dans un même objet, facilitant l’organisation et la réutilisation du code.
- Une classe définit le modèle ; un objet (ou instance) est une réalisation concrète de ce modèle, avec ses propres valeurs d’attributs.
- La méthode
__init__initialise chaque objet à sa création ;selfdésigne l’objet courant. - L’interface d’une classe (ses méthodes publiques et leur spécification) est ce que l’utilisateur doit connaître ; l’encapsulation impose de passer par ces méthodes pour lire ou modifier les attributs, dont l’implémentation peut changer sans toucher au reste du programme.
- Les méthodes spéciales (
__repr__,__add__,__eq__, etc.) permettent de personnaliser l’affichage, les opérateurs et les comparaisons. - La POO est la brique de base pour implémenter les structures de données du programme (listes chaînées, piles, files, arbres).
Indices pour le TP Capytale
Le TP Capytale de ce chapitre (bouton « TP Capytale » en haut de la page) propose un indice sous chaque consigne. Les voici, partie par partie : n’ouvrez que celui dont vous avez besoin.
Partie 1 : compléter le constructeur de Voiture
Chaque attribut se crée par une affectation self.nom_attribut = valeur. La marque et le modèle viennent des paramètres du constructeur ; le kilométrage et la vitesse valent 0 au départ.
Partie 1 : créer trois voitures
Un objet se crée en appelant la classe comme une fonction, avec les paramètres du constructeur sauf self : par exemple Voiture("Renault", "Clio").
Partie 2 : méthodes accelerer et rouler
Dans une méthode, les attributs de l’objet s’écrivent self.vitesse et self.km. Accélérer de delta revient à ajouter delta à self.vitesse. La distance parcourue vaut self.vitesse * duree ; on l’ajoute à self.km.
Partie 3 : méthode __str__
__str__ renvoie avec return une chaîne construite par une f-string à partir des attributs, par exemple f"{self.marque} ...". Comparer avec la chaîne attendue dans le assert, espaces et parenthèses compris.
Partie 4 : classe VoitureElectrique
super().__init__(...) appelle le constructeur de Voiture : lui passer les deux paramètres qu’il attend, marque et modele. L’attribut autonomie se crée ensuite comme les autres, avec self..
Partie 4, pour aller plus loin : redéfinir rouler
La distance et le kilométrage se calculent comme dans rouler de la classe Voiture. L’autonomie diminue de la distance parcourue.
Partie 5 : classe Compte
Même démarche que pour Voiture : le constructeur crée self.titulaire et self.solde ; depot et retrait modifient self.solde. Dans retrait, vérifier avec un assert, avant de retirer, que le montant ne dépasse pas le solde.
Partie 5 : code à copier puis compléter
Copier ce code dans la cellule de la classe Compte du TP, à la place de la classe incomplète, puis remplacer chaque ....
class Compte:
def __init__(self, titulaire, solde_initial):
self.titulaire = ...
self.solde = ...
def depot(self, montant):
self.solde = ...
def retrait(self, montant):
assert ..., "Solde insuffisant"
self.solde = ...
def __str__(self):
return f"Compte de {...} : {...} €"