Classes et objets

Un objet est un bout de code auquel est associé :

Un objet, n'est donc pas isolé, il partage ses fonctionnalités avec tous les objets de sa classe.

Créer ses propres classes

De façon générale, on peut définir un objet et une classe comme :

Définition

Un objet est une structure de données (les champs de la structure de donnée sont appelés attributs) sur laquelle on peut effectuer des opérations (appelées méthodes).

Pour pouvoir facilement créer une structure particulière et donner un moyen simple d'effectuer les opérations sur celle-ci, on utilise des classes comme patron de ces objets :

  • elles vont gérer la création des objets via un constructeur.
  • elles contiennent dans leur espace de noms les différentes méthodes que l'on pourra appliquer

On crée une classe par rapport à un besoin que l'on veut satisfaire. Supposons que l'on veuille créer une classe compteur qui permettent d'exécuter le code suivant :

from compteur import Compteur

c1 = Compteur()
c2 = Compteur()
c1.incrémente()
c2.incrémente()
c1.incrémente()

print(c2.valeur)

Pour construire cette cette classe, il faut se poser la question :

Analyse du besoin

Ici notre besoin c'est de faire marcher le bout de code d'une façon plausible. Il faut se demander ce que veut l'utilisateur de ce code python.

Le programme commence par importer le mot Compteur d'un module nommé compteur (donc placé dans un fichier nommé compteur.py dans le dossier du projet) et on l'exécute 2 fois pour l'affecter à 2 noms différents. Pour voir ce que peut être Compteur, plusieurs indices :

A la lecture du code, on a donc envie que le code :

  1. création de deux compteurs
  2. en incrémente un deux fois et l'autre qu'une seule fois
  3. affiche à l'écran la valeur d'un des compteurs (celui qui a été incrémenté une fois) qu'on suppose égale à 1

Un code dont les objets sont bien nommés doit pouvoir se lire et être interprétable sans connaître le corps des fonctions et méthodes utilisées.

Les fonctionnalités que notre compteur doit avoir sont :

Pour que l'on puisse avoir plusieurs compteurs (si on n'a qu'un seul compteur, ce n'est pas la peine de faire des objets), il faut que chaque compteur ait une valeur à lui : valeur est un attribut. En revanche, on veut pouvoir incrémenter tous les compteurs : incrémente() est une méthode.

Modélisation UML

Pour représenter les différentes possibilités offertes par une classe on utilise l'Unified Modeling Language (UML)

Vous pouvez suivre ce petit tutoriel UML pour comprendre sa notation et son utilité.

L'UML peut être très compliqué. Nous allons uniquement l'utiliser ici comme une représentation synthétique d'une classe/objet. Vous le verrez dans les exemples ci-dessous mais, en gros, une classe en UML c'est le diagramme :

une classe UML

On peut même combiner les diagramme UML ensemble. Par exemple un point et un polygone :

point / polygone

Ou le classique personne et compte bancaire :

personne / compte

UML du Compteur

On va essayer de comprendre le code pour produire une représentation UML de la classe Compteur. L'analyse du besoin que l'on a effectué nous a permit de définir l'usage ue l'on veut faire du compteur :

Ce qui donne le diagramme UML du compteur :

compteur

À retenir

Pour créer un diagramme UML :

  1. on commence toujours par le nom de la classe
  2. on explicite ses méthodes, c'est à dire comment on va utiliser les objets (ici incrémenter un compteur).
  3. on crée la structure de données qui va permettre de stocker les informations nécessaires à son utilisation : ce sont les attributs (ici un entier pour stocker le nombre de fois où on l'a incrémenté).

Implémentation en python

La modélisation UML ne nous indique pas l'implémentation des différentes méthodes, il nous indique juste comment on peut les utiliser. Pour exécuter et utiliser notre compteur, il faut le coder.

N'hésitez pas à jeter un coup d'œil au tutoriel de python sur ses classes. Ce cours est là pour vous montrer tout ce qu'il y a dedans, à part (peut-être) la partie sur l'héritage et les itérateurs.

La modélisation UML se transcrit presque mot pour mot en python. En codant la classe suivante dans un fichier nommé compteur.py l'exemple va fonctionner :

class Compteur:
    def __init__(self):
        self.valeur = 0

    def incrémente(self):
        self.valeur = self.valeur + 1

On va détailler plus tard les méthodes et moyens de construire des classes en python mais de l'écriture de la classe compteur on peut d'ores et déjà en déduire que :

Classes en python

Définition de classes

La définition d'une classe est un bloc python contenant deux parties :


class <nom de la classe>:
    def __init__(self, paramètre 1, ..., paramètre n):
        # création des attributs
        # initialisation de l'objet
    def méthode(self, paramètre 1, ..., paramètre n_1):
        # ...
        # instructions de la méthode
        # ...
    # autres méthodes

En python, toutes les méthodes sont des fonctions définies dans le bloc classe :

La méthode __init__ n'a pas de return, mais elle est utilisée dans le processus de création d'un objet.

De façon formelle :

Définition

Une classe en python est un objet de type classe contenant un espace de nommage.

Reprenons l'exemple de la classe compteur et examinons son exécution. à la fin de la ligne 1, on vient d'importer le module compteur qui contient uniquement une définition de classe, on est dans le cas suivant :

L'espace de nommage de la classe Compteur contient deux méthodes (les fonctions définies dans une classes sont appelées méthodes) :

Les deux méthodes prennent comme premier paramètre un objet nommé self, qui est la manière explicite de python de montrer quel objet est utilisé lors de l'appel de méthodes :

Définition

le premier paramètre de toute méthode, noté self, est l'objet sur lequel on va appliquer la méthode (l'objet à gauche du . lors de l'appel à celle-ci par une notation pointée).

Vous pouvez appeler ce premier paramètre comme vous voulez, mais il est très très déconseillé de le faire car votre code en deviendra moins lisible (tout le monde utilise le nom self).

Création d'un objet

En python, le retour de l'exécution d'une classe (l'utilisation de la classe comme si c'était une fonction) produit un objet. Par exemple :

Certains objets se créent juste avec leur valeur comme les entiers, les réels ou encore les chaines de caractères. En python 3 est équivalent à int(3) par exemple.

Le code suivant :

o = MaClasse(paramètre_1, ..., paramètre_n)

Va créer un objet de type MaClasse en effectuant les différentes étapes suivantes :

  1. créant un objet vide o de type MaClasse contenant un espace de nommage dont le parent est l'espace de nommage de sa classe
  2. il exécute le constructeur __init__ sur l'objet : MaClasse.__init__(o, paramètre 1, ..., paramètre n) (c'est pour ça que la méthode __init__ n'a pas de retour)

En prenant l'exemple du compteur. l'exécution de la ligne 3 (c1 = Compteur()) se déroule comme suit :

  1. Création d'un nouvel objet :
  2. Exécution du constructeur __init__ avec le nouvel objet en paramètre. À la fon de l'exécution du constructeur on est dans la configuration suivante :

On refait pareil pour la ligne 4 (c2 = Compteur()), ce qui fait qu'on se trouve dans l'état suivant :

Comme le constructeur est toujours appliqué au nouvel objet créé chaque objet va bien avoir des attributs distincts !

À retenir

En python, les attributs d'un objet sont explicitement créés dans le constructeur.

Exécution de méthodes

L'exécution de méthodes se fait simplement en utilisant les règles des espaces de nommages et de la notation pointée.

  1. on cherche le nom (potentiellement récursivement) dans l'espace de nommage
  2. l'objet appelant est donné comme premier paramètre de la méthode.

Ainsi pour l'exemple du compteur, l'exécution de la ligne 5 (c1.incrémente()) se déroule comme suit :

  1. on cherche le nom incrémente dans l'espace de nommage de c1. Il n'y est pas
  2. on cherche alors le nom incrémente dans l'espace de nommage parent, c'est celui de sa classe et on le trouve !
  3. on exécute la méthode incrémente en plaçant l'objet appelant (ici c1) en premier paramètre de la méthode
  4. La méthode accède aux différents attributs de l'objet en utilisant la notation pointée ((ici self.valeur))

Le résultat de l'exécution de la ligne 5 est alors :

À vous pour vérifier que vous avez compris :

Donnez le schéma des espaces de nommages jusqu'après l'exécution de la ligne 7 de l'exemple du compteur.

corrigé

Détaillez l'exécution de la ligne 9 de l'exemple du compteur (print(c2.valeur)).

corrigé

  1. on cherche à exécuter la fonction print : il faut trouver l'objet qui est son premier paramètre
  2. son paramètre est l'objet associé au nom valeur dans l'espace de nommage de l'objet associé au nom c2
  3. le nom c2 existe dans l'espace des variables, c'est un objet de la classe Compteur
  4. L'espace de nommage de cet objet contient le nom valeur qui est associé à un entier valant 1 : c'est notre paramètre de la fonction print
  5. on affiche à l'écran un entier valant 1.