Aller au contenu principal

Module 3 — Fonctions, modules et organisation du code

Le code d'analyse commence toujours en script linéaire — et c'est très bien. Mais dès qu'un traitement se répète ou qu'un projet dépasse l'après-midi, les fonctions et les modules deviennent la différence entre un travail réutilisable et un fichier de 800 lignes que même son auteur redoute d'ouvrir.

L'anatomie d'une fonction propre

def taux_valeurs_manquantes(colonne, seuil_alerte=0.2):
"""Calcule la part de valeurs manquantes d'une colonne.

Renvoie un tuple (taux, alerte) où alerte vaut True si le
taux dépasse seuil_alerte.
"""
taux = colonne.isna().mean()
return taux, taux > seuil_alerte

Quatre choix de conception dans ces huit lignes, tous généralisables.

Un nom qui dit ce qu'elle fait — verbe ou substantif précis, pas process_data. Si le nom exige un « et » (nettoyer_et_sauvegarder), la fonction fait deux choses : découpez-la.

Une valeur par défaut pour le paramètre secondaire : l'appel courant reste simple, le cas particulier reste possible.

Une docstring en première ligne : une phrase sur le rôle, une sur le retour. C'est ce qu'affiche help(), et c'est la documentation qui ne se désynchronise jamais du code puisqu'elle vit dedans.

Un retour explicite. Une fonction qui print au lieu de return est inutilisable en aval : on ne peut ni tester son résultat, ni le passer à la suite du pipeline.

Arguments positionnels et nommés

Python permet d'appeler par position, par nom, ou en mélange :

lire_csv("ventes.csv", ";", "utf-8", True)                     # illisible
lire_csv("ventes.csv", sep=";", encoding="utf-8", header=True) # clair

La règle pratique : au-delà de deux arguments, nommez-les à l'appel. Les bibliothèques de données l'imposent presque : un appel réel à pd.read_csv aligne facilement cinq arguments nommés, et c'est précisément ce qui le rend relisible.

Les signatures *args et **kwargs (nombre variable d'arguments positionnels / nommés) se lisent plus qu'elles ne s'écrivent au quotidien : elles expliquent pourquoi les fonctions de tracé de Matplotlib acceptent des dizaines d'options sans les déclarer une à une.

La portée des variables : locale d'abord

Les variables créées dans une fonction sont locales : elles naissent à l'appel et meurent au retour. Une fonction peut lire une variable globale, mais la modifier exige le mot-clé global — et c'est presque toujours une mauvaise idée.

Les fonctions qui dépendent de l'extérieur

Une fonction qui lit des variables globales (df, config…) fonctionne dans le notebook où elle est née et nulle part ailleurs. La discipline qui change tout : tout ce dont la fonction a besoin entre par ses paramètres ; tout ce qu'elle produit sort par son retour. Ce principe rend le code testable, déplaçable — et il désamorce la moitié des problèmes de notebooks du module 10.

Modules et imports : réutiliser sans copier-coller

Tout fichier .py est un module importable. Un projet de données typique se structure ainsi :

projet/
├── nettoyage.py # fonctions de préparation
├── visualisation.py # fonctions de tracé
├── analyse.ipynb # le notebook qui les utilise
└── requirements.txt # les dépendances (module 9)
# dans analyse.ipynb
from nettoyage import taux_valeurs_manquantes, normaliser_colonnes
import visualisation as viz

Les conventions d'import de l'écosystème sont quasi rituelles — les respecter rend votre code immédiatement familier à tout lecteur :

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

Deux pratiques à éviter : from module import * (on ne sait plus d'où vient chaque nom) et les imports au milieu du fichier (tous en tête, standard d'abord, tiers ensuite, locaux enfin).

Le bloc if __name__ == "__main__"

Ce garde-fou, présent dans tout script sérieux, distingue deux usages du même fichier :

# nettoyage.py
def normaliser_colonnes(df):
...

if __name__ == "__main__":
# exécuté seulement par : python nettoyage.py
# PAS lors d'un import depuis le notebook
df = pd.read_csv("brut.csv")
print(normaliser_colonnes(df).head())

Sans lui, importer le module exécuterait le code de test — chargement de fichier compris. Avec lui, le fichier est à la fois une bibliothèque importable et un script exécutable.

Gérer les erreurs sans les cacher

try:
df = pd.read_csv(chemin)
except FileNotFoundError:
print(f"Fichier absent : {chemin} — vérifier le montage des données")
raise

Les deux règles qui évitent les pièges classiques : attraper l'exception précise (jamais except: nu, qui avale aussi les vraies erreurs et les interruptions clavier), et ne jamais étouffer en silence — traiter le cas, ou relancer avec raise. Un pipeline qui continue sur des données à moitié chargées produit des résultats faux avec un air parfaitement sain.

Ce qu'il faut retenir

  • Une fonction : un rôle, des entrées par paramètres, une sortie par return, une docstring d'une ou deux phrases.
  • Arguments nommés dès que l'appel dépasse deux paramètres ; valeurs par défaut pour les options.
  • Un projet = des modules .py importés par le notebook ; conventions np, pd, plt, sns ; imports en tête de fichier.
  • if __name__ == "__main__" sépare l'usage bibliothèque de l'usage script.
  • Attraper les exceptions précises, ne jamais étouffer une erreur en silence.

Au module suivant, le cœur du calcul scientifique : NumPy, ses tableaux et la vectorisation qui remplace les boucles.