1.2. Les classes

Avant de pouvoir utiliser un objet, il faut créer un modèle qui le décrit et sur la base duquel il sera construit. Ce modèle s’appelle une classe et on le déclare à l’aide du mot-clé class suivi du nom de la classe et du symbole :. Le corps de la classe vient ensuite, dans un nouveau bloc de code indenté. On y déclare les différents attributs (propriétés) de la classe, ainsi que les méthodes (fonctions) qui définissent l’objet modélisé.

class ClassName:

    # attributs
    property = value
    # ...

    # méthodes
    def method(self):
      return result
    # ...

Note

Par convention les noms de classes commencent par une lettre majuscule et utilisent la « notation chameau ». Cela signifie que les mots sont démarqués en les débutant par une lettre majuscule au lieu de les séparer par le symbole _ (tiret bas).

1.2.1. L’instanciation

Une fois la classe écrite pour un objet, il est possible d’en créer des instances, c’est-à-dire des objets qui sont des exemplaires concrets de la classe. L’instanciation se fait lors de l’initialisation d’une variable, en spécifiant le nom de la classe comme on le ferait pour un appel de fonction. On accède ensuite aux attributs d’une instance de classe grâce à leurs noms en utilisant la notation pointée.

class Tree:
    pass


apple_tree = Tree()
apple_tree.branches = 23
apple_tree.leaves = 3942

binary_tree = Tree()
binary_tree.branches = 14
binary_tree.leaves = 8

print(f"Mon pommier a {apple_tree.branches} branches et {apple_tree.leaves} feuilles.")
print(f"Mon arbre binaire a {binary_tree.branches} branches et {binary_tree.leaves} feuilles.")
Mon pommier a 23 branches et 3942 feuilles.
Mon arbre binaire a 14 branches et 8 feuilles.

Note

Les attributs d’instances ne sont crées qu’au moment où on leur assigne une valeur. Ainsi, différentes instances d’une même classe peuvent avoir des ensembles d’attributs différent. Dans l’exemple ci-dessus, si l’on omet par exemple de donner une valeur à l’attribut leaves de notre pommier, la variable correspondant n’existera pas et une erreur se produira lorsque l’on tentera de l’afficher.

Nous observons grâce à cet exemple qu’une classe décrit un concept abstrait qui peut ensuite prendre différentes formes. La classe Tree embrasse le concept de l’arbre dans sa généralité alors qu’un arbre concret correspond à une instance spécifique. Une classe représente donc une infinité d’objets ayant tous un certain nombre de traits en commun.

1.2.2. La définition de classe

Reprenons notre exemple d’université et écrivons une classe University, dont nous allons créer une instance u_micro pour l’université de Micropolis.

class University:    # on nomme la classe `University`
    pass


# on crée une instance de la classe `University`
u_micro = University()
# puis, on assigne des valeurs aux attributs
u_micro.name = "Micropolis"
u_micro.nb_students = 4000
u_micro.nb_professors = 150
u_micro.faculties = ["Droit", "Économie", "Lettres", "Sciences"]
# enfin, on affiche les attributs
print(f"Université de {u_micro.name}")
print(u_micro.nb_students)
print(u_micro.nb_professors)
print(u_micro.faculties)
Université de Micropolis
4000
150
['Droit', 'Économie', 'Lettres', 'Sciences']

Toutes les propriétés de notre université sont regroupées au sein de l’objets u_micro, créé à partir du modèle (actuellement vide) défini par la classe University. On remarque immédiatement que notre code devient plus lisible et mieux structuré que lorsque l’on travaillait avec des variables indépendantes.

1.2.3. Les méthodes

Les méthodes sont des fonctions appartenant à une classe, de la même manière qu’un attribut est une variable interne à un classe. Elles sont déclarées dans le corps de la classe comme des fonctions normales et on les appelle à l’aide de la notation pointée en spécifiant une instance de la classe. Les méthodes correspondent aux actions qui peuvent être effectuées par les objets. Ainsi, une classe Sheep modélisant un mouton se verrait pourvue de méthodes eat(), sleep() et bleat() (pour manger, dormir et bêler).

La principale différence entre les fonctions et les méthodes est que ces dernières doivent toutes obligatoirement posséder un paramètre nommé self, en première position. Ce paramètre désigne l’instance « sur laquelle » est appelée la méthode (la variable à gauche du symbole . lors d’un appel de méthode à l’aide de la notation pointée). Sa valeur lui est automatiquement assignée et il est ajouté de manière complètement transparente. Il ne compte donc pas comme un paramètre positionnel. À l’intérieur de la méthode, self permet d’accéder aux attributs et aux éventuelles autres méthodes de l’instance.

class University:

    def present(self, long=True):  # on définit une méthode de présentation avec 2 variantes
        print(f"L'université de {self.name} compte {self.nb_students} étudiants "
              f"et {self.nb_professors} professeurs dans {len(self.faculties)} facultés :")
        if long:  # variante longue (toutes les facultés)
            for fac in self.faculties:
                print(f"- {fac}")
        else:  # variante courte
            for i in range(len(self.faculties)):
                print(f"- {self.faculties[i]}")
                if i == 1 and len(self.faculties) > 3:
                    print("- ...")
                    break


# Micropolis
u_micro = University()

u_micro.name = "Micropolis"
u_micro.nb_students = 4000
u_micro.nb_professors = 150
u_micro.faculties = ["Droit", "Économie", "Lettres", "Sciences"]

# Megapolis
u_mega = University()

u_mega.name = "Megapolis"
u_mega.nb_students = 15000
u_mega.nb_professors = 500
u_mega.faculties = ["Administration", "Droit", "Géosciences", "HEC", "Lettres",
                    "Médecine", "Politique", "Théologie"]

u_micro.present()      # variante longue par défaut
u_mega.present(False)  # variante courte
L'université de Micropolis compte 4000 étudiants et 150 professeurs dans 4 facultés :
- Droit
- Économie
- Lettres
- Sciences
L'université de Megapolis compte 15000 étudiants et 500 professeurs dans 8 facultés :
- Administration
- Droit
- ...

La méthode University.present() permet maintenant à l’instance sur laquelle elle est appelée de se présenter en utilisant directement ses propres attributs. Ainsi, le résultat de cette méthode dépend à la fois des paramètres reçus et de l’instance courante.

1.2.4. Le constructeur

Avant d’appeler une méthode d’une instance, il est essentiel de s’assurer que tous les attributs de l’instance ont bien été crées et initialisés, sans quoi une erreur pourra se produire à l’exécution (par exemple dans la méthode de présentation de l’université en cas d’accès à des attributs inexistants). Cette tâche d’initialisation est fastidieuse et peut facilement conduire à des erreurs ou des oublis. Pour simplifier cette opération et la rendre plus sûre, les classes possèdent une méthode particulière, appelée le constructeur, qui permet d’initialiser les propriétés de l’objet. Le constructeur est automatiquement appelé lors de la création de nouvelles instances. La méthode se nomme __init__(self, par1, par2, ...) et reçoit des paramètres transmis au moment de l’instanciation de la classe instance = ClassName(val2, val2, ...). L’utilité du constructeur est donc d’initialiser les attributs de la classe selon les paramètres reçus, généralement de la manière suivante.

class ClassName:

    # constructeur
    __init__(self, par1, par2, ...):
        self.property1 = par1
        self.property2 = par2
        # ...

Note

Les méthodes dont le nom commence et se termine par deux symboles _ (tiret bas) sont des méthodes spéciales du langage qui ont un rôle particulier.

Écrivons un constructeur pour la classe University.

class University:

    # le constructeur possède 4 paramètres nécessaires à l'initialisation de l'instance
    def __init__(self, name, nb_students, nb_professors, faculties):
        # notez la différence entre `self.name` (attribut de l'instance) et `name` (paramètre)
        self.name = name
        self.nb_students = nb_students
        self.nb_professors = nb_professors
        self.faculties = faculties

    def present(self, long=True):
        print(f"L'université de {self.name} compte {self.nb_students} étudiants "
              f"et {self.nb_professors} professeurs dans {len(self.faculties)} facultés :")
        if long:
            for fac in self.faculties:
                print(f"- {fac}")
        else:
            for i in range(len(self.faculties)):
                print(f"- {self.faculties[i]}")
                if i == 1 and len(self.faculties) > 3:
                    print("- ...")
                    break


# on fournit les paramètres souhaités pour l'initialisation de l'instance
u_micro = University("Micropolis", 4000, 150,
                     ["Droit", "Économie", "Lettres", "Sciences"])
u_mega = University("Megapolis", 15000, 500,
                    ["Administration", "Droit", "Géosciences", "HEC", "Lettres",
                     "Médecine", "Politique", "Théologie"])
u_micro.present()
u_mega.present(False)

Le résultat affiché dans la console est identique à la version précédente de notre programme, mais le code est bien plus simple et élégant. Il est également plus robuste car il garantit que toutes les instances possèdent bien le même ensemble d’attributs.

On mentionnera encore que les paramètres du constructeur peuvent posséder une valeur par défaut, comme n’importe quelle fonction ou méthode.