La programmation objet en Python

Objectifs et prérequis

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

La programmation objet consiste à regrouper données et traitements dans une même structure appelée objet. C’est un paradigme - manière de voir - de programmation qui a l’avantage de localiser en un même endroit toute l’implémentation d’une structure de données abstraite.

  • Les données associées à un objet sont appelés des attributs.
  • Les fonctions ou procédures s’appliquant sur un objet sont appelées des méthodes.
  • Une classe est à la fois un modèle d’objet et une machine à fabriquer des objets sur ce modèle.

Un premier exemple : la classe Point

On cherche à définir une classe pour représenter des points à afficher dans un programme de dessin. Chaque point a ses coordonnées x et y. La méthode translater permet de modifier un point.

  • Les attributs d’un point p sont notés usuellement avec la notation pointée p.x et p.y.
  • L’application d’une méthode est aussi notée par la notation pointée : p.translater(dx, dy)

Une première classe en Python

La premiere notation à connaître est la notation de classe. Le nom donné à une classe est le nom du constructeur d’objets de cette classe. Par convention, les noms de classes en Python commencent par une majuscule (Point, CompteBancaire).

Toutes les fonctions définies de manière interne à une classe sont des méthodes. Leur premier paramètre fait référence à l’objet sur lequel est appelée la méthode.

class Point:
    """Manipulation de points."""

    def placer(self, abscisse, ordonnee):
        """Place le point aux coordonnées indiquées"""
        self.x, self.y = abscisse, ordonnee

    def translater(self, dx, dy):
        """Translate le point selon le vecteur (dx, dy)"""
        self.placer(self.x + dx, self.y + dy)

On peut créer un objet p par appel du constructeur prédéfini de la classe. On peut ensuite appeler les différentes méthodes sur cet objet avec la notation pointée. C’est l’objet p qui est alors passé comme valeur du paramètre self.

Remarque 1. En Python, le mot self n’est pas un mot clé. C’est une simple convention d’usage de nommer ainsi le premier paramètre d’une méthode.

p = Point()
p.placer(3, 7)
print(p.x, p.y)
3 7
p.translater(1, 2)
print(p.x, p.y)
4 9

Les attributs x et y sont propres à chaque objet. Dans la terminologie des langages à objet, on parle d’ attributs d’instance.

Remarque 2. En Python, c’est par la notation self.x que l’attribut x est créé pour l’objet sur lequel est appelée la méthode.

Création et initialisation d’un objet en Python

La création standard d’un objet par la méthode de sa classe, crée des objets sans attributs. Pour que les méthodes puissent s’appliquer sans erreur, il faut cependant que tous les objets d’une classe possèdent certains attributs. Dans l’exemple de la classe Point, la méthode translater ne peut s’appliquer qu’à des objets ayant les attributs x et y.

C’est pour cette raison, qu’une méthode d’initialisation des objets créant des attributs est nécessaire. En Python, c’est la méthode spéciale __init__, si elle est définie dans la classe, qui est systématiquement appelée sur chaque nouvel objet juste après sa création.

class Point:
    """Manipulation de points."""

    def __init__(self, abscisse, ordonnee):
        """Initialise le point avec les coordonnées indiquées"""
        self.x, self.y = abscisse, ordonnee

    def translater(self, dx, dy):
        """Translate le point selon le vecteur (dx,dy)"""
        self.x += dx
        self.y += dy

On peut alors créer un point en fournissant les valeurs des paramètres prévus dans la fonction spéciale d’initialisation.

Si tous les points de cette classe sont créés ainsi, ils disposent alors des attributs usuels d’un point et peuvent être translatés, sans problème.

p = Point(12, 6)
p.translater(1, 2)
print(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
  1. Quelle est la différence entre une classe et un objet ?
    RéponseUne 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.
  2. Pourquoi la méthode __init__ est-elle importante ?
    RéponseSans __init__, un objet nouvellement créé n'a aucun attribut. Les méthodes qui accèdent à ces attributs provoqueraient une erreur. __init__ garantit que chaque objet est dans un état cohérent dès sa création.
  3. Que représente self dans une méthode ?
    Réponseself est une référence vers l'objet sur lequel la méthode est appelée. Quand on écrit p.translater(1, 2), Python passe automatiquement p comme valeur de self.

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), translater(dx, dy) et l’addition, sans lire le code des méthodes.

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

En réalité, objets et classes sont omniprésents dans le langage Python, car ils servent à l’implémentation des structures de données et bibliothèques du langage.

On le constate en particulier avec la fonction dir qui liste tous les attributs et méthodes d’un objet.

Cette fonction peut être appelée pour un objet créé par le programmeur.

print(dir(p))
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'translater', 'x', 'y']

dir peut aussi être appelé pour un objet prédéfini du langage, une liste par exemple. On y découvre toutes les méthodes applicables à une liste.

L = [1, 8, 7]
print(dir(L))
['__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']

On constate qu’il y a de nombreuses méthodes spéciales repérables par leur nom encadré de __.

Méthodes spéciales en Python

Ces méthodes sont appelées dans des contextes particuliers et peuvent être redéfinies par le programmeur pour une classe particulière.

  • A l’initialisation d’un objet, c’est la méthode : __init__(self, ...)
  • Lors de la conversion en chaîne par str(obj) ou affichage avec print(obj), c’est la méthode : __str__(self)
  • La conversion en chaîne « évaluable » par repr(obj) utilise la méthode __repr__(self) qui peut aussi être utilisée par str ou print, si __str__ n’est pas définie.
  • Le test d’égalité entre deux objets, invoque la méthode __eq__(self, other)
  • L’addition d’un objet avec un autre utilise la méthode __add__(self, other)

Voir ici pour la liste complète.

Suite de l’exemple de la classe Point

On utilise ces méthodes spéciales pour permettre d’afficher une représentation externe d’un point et proposer une addition de deux points.

class Point:
    """Manipulation de points."""
    def __init__(self, abscisse, ordonnee):
        """Initialise le point avec les coordonnées indiquées"""
        self.x, self.y = abscisse, ordonnee

    def translater(self, dx, dy):
        """Translate le point selon le vecteur (dx,dy)"""
        self.x += dx
        self.y += dy

    def __add__(self, other):
        return Point(self.x + other.x, self.y + other.y)

    def __repr__(self):
        return (str(self.x) + "," + str(self.y))
p = Point(1, 5)
q = Point(3, 8)
print(p + q)
4,13
L'essentiel à retenir
  • 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 ; self dé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).