Modularité

Objectifs et prérequis

Prérequis : fonctions en Python , programmation orientée objet

À l’issue de ce chapitre, vous saurez :

  • découper un programme en modules et les importer (import, from ... import) ;
  • documenter des fonctions et des modules avec des docstrings et help() ;
  • écrire et exécuter des tests intégrés à la documentation avec doctest ;
  • distinguer l’API publique d’un module de son implémentation interne ;
  • organiser des modules en paquets (packages).

Pourquoi la modularité ?

Lorsqu’un programme dépasse quelques dizaines de lignes, il devient difficile à lire, à maintenir et à faire évoluer si tout le code est dans un seul fichier. La modularité consiste à découper un programme en modules : des fichiers indépendants, chacun responsable d’une tâche bien définie.

Les avantages sont multiples :

  • la lisibilité : chaque module a un rôle clair ;
  • la réutilisabilité : un module bien conçu peut être utilisé dans d’autres projets ;
  • le travail collaboratif : plusieurs développeurs peuvent travailler sur des modules différents sans se gêner ;
  • la testabilité : on peut tester chaque module indépendamment.

Créer un module en Python

Un module Python est simplement un fichier .py contenant des définitions de fonctions, de classes ou de variables. Par exemple, créons un fichier geometrie.py :

"""Module de calculs géométriques simples."""

import math

def aire_cercle(rayon):
    """Renvoie l'aire d'un cercle de rayon donné.

    >>> aire_cercle(1)
    3.141592653589793
    >>> round(aire_cercle(5), 2)
    78.54
    """
    return math.pi * rayon ** 2

def perimetre_cercle(rayon):
    """Renvoie le périmètre d'un cercle de rayon donné.

    >>> round(perimetre_cercle(1), 2)
    6.28
    """
    return 2 * math.pi * rayon

def aire_rectangle(longueur, largeur):
    """Renvoie l'aire d'un rectangle.

    >>> aire_rectangle(3, 4)
    12
    """
    return longueur * largeur

Ce fichier est un module. On peut l’utiliser depuis un autre fichier Python situé dans le même répertoire :

import geometrie

print(geometrie.aire_cercle(10))
print(geometrie.perimetre_cercle(10))
314.1592653589793
62.83185307179586

On peut aussi n’importer que certaines fonctions :

from geometrie import aire_cercle, aire_rectangle

print(aire_cercle(5))
print(aire_rectangle(3, 7))
78.53981633974483
21

Documenter un module

Un module bien documenté commence par une docstring (chaîne de documentation) qui décrit son rôle. Chaque fonction publique possède également sa propre docstring, qui précise ce qu’elle fait, ses paramètres et sa valeur de retour.

La fonction help() de Python permet d’afficher la documentation d’un module ou d’une fonction :

import geometrie
help(geometrie.aire_cercle)
Help on function aire_cercle in module geometrie:

aire_cercle(rayon)
    Renvoie l'aire d'un cercle de rayon donné.

    >>> aire_cercle(1)
    3.141592653589793
    >>> round(aire_cercle(5), 2)
    78.54

Les docstrings contenant des exemples au format >>> peuvent être testées automatiquement avec le module doctest :

import doctest
import geometrie

doctest.testmod(geometrie)

Si tous les exemples produisent le résultat attendu, aucune erreur n’est affichée. Sinon, doctest signale les écarts.

Utiliser une API ou une bibliothèque

Une API (application programming interface, interface de programmation) est l’ensemble des fonctions, classes et constantes qu’un module met à disposition de ses utilisateurs, avec leur spécification. C’est l’interface publique du module : on l’utilise sans connaître son implémentation, et c’est elle que décrivent help() et la documentation officielle.

En Python, de nombreuses bibliothèques fournissent des fonctionnalités prêtes à l’emploi. Pour les utiliser efficacement, il faut savoir exploiter leur documentation.

Prenons l’exemple du module random de la bibliothèque standard :

import random

help(random.choice)
Help on method choice in module random:

choice(seq) method of random.Random instance
    Choose a random element from a non-empty sequence.

La documentation indique que choice prend en paramètre une séquence non vide et renvoie un élément choisi au hasard. À partir de cette information, on peut l’utiliser :

import random

couleurs = ['rouge', 'vert', 'bleu', 'jaune']
print(random.choice(couleurs))

Activité. En utilisant uniquement la fonction help() et la documentation officielle de Python, écrire un programme qui :

  1. génère une liste de 20 entiers aléatoires entre 1 et 100 (utiliser random.randint) ;
  2. trie cette liste par ordre croissant sans utiliser la méthode sort (chercher dans la documentation de sorted) ;
  3. affiche la médiane de cette liste (chercher dans le module statistics).

Structurer un projet en modules

Pour un projet plus conséquent, on organise les fichiers en paquets (packages). Un paquet est un répertoire contenant un fichier __init__.py (qui peut être vide) et plusieurs modules.

Exemple de structure pour un projet de jeu :

mon_jeu/
├── __init__.py
├── personnage.py
├── inventaire.py
├── combat.py
└── affichage.py

Chaque fichier s’occupe d’un aspect du jeu. On peut alors écrire :

from mon_jeu.personnage import Guerrier
from mon_jeu.inventaire import Inventaire

heros = Guerrier("Arthur", points_de_vie=100)
sac = Inventaire()
sac.ajouter("épée")

Cette organisation rend le code navigable et maintenable, même pour des projets de plusieurs milliers de lignes.

L'essentiel à retenir
  • Un module est un fichier Python contenant des définitions réutilisables ; on l’importe avec import ou from ... import ....
  • Chaque fonction publique doit avoir une docstring décrivant son rôle, ses paramètres et des exemples testables avec doctest.
  • La fonction help() permet d’explorer la documentation d’un module ou d’une fonction sans lire le code source : c’est l’API du module.
  • Un paquet (package) est un répertoire de modules, identifié par la présence d’un fichier __init__.py.
  • La modularité favorise la lisibilité, la réutilisabilité et le travail collaboratif ; elle est au cœur de la mise au point des programmes .