Passer du notebook au script Python : la méthode en 6 étapes pour un code data qui tourne ailleurs que chez vous
Un code production-ready, c'est un code qui produit le même résultat quand quelqu'un d'autre l'exécute sur une autre machine, six mois plus tard, sans vous poser de question. Un notebook n'a presque jamais cette propriété : ordre d'exécution implicite, chemins absolus, variables fantômes, dépendances non déclarées.
La bonne nouvelle : passer de l'un à l'autre n'est pas un projet de trois semaines. C'est une séquence de 6 gestes mécaniques, applicables en une demi-journée sur un notebook existant.
Pourquoi votre notebook ne survit pas hors de votre machine
Les quatre causes reviennent tout le temps :
| Cause | Symptôme observé | Coût réel |
|---|---|---|
| Ordre d'exécution non linéaire | « Chez moi ça marchait » après un Restart Kernel | 1 à 3 h de debug par incident |
Chemins absolus (C:/Users/moi/...) |
FileNotFoundError chez le collègue |
Le notebook n'est jamais réutilisé |
| Dépendances non déclarées | ModuleNotFoundError au 3e import |
Recréation d'un env à l'aveugle |
| Constantes noyées dans le code | Il faut relire 400 lignes pour changer une date | Personne n'ose y toucher |
Aucune de ces causes n'est un problème de compétence Python. Ce sont des problèmes d'emballage. C'est précisément ce que couvre le software engineering appliqué à la data.
Étape 1 : rendre le notebook rejouable avant de le découper
Ne réécrivez rien tant que le notebook n'est pas reproductible en l'état.
Le test : Kernel > Restart & Run All. Si ça casse, vous avez une variable fantôme, une cellule supprimée dont le résultat vit encore en mémoire, ou une dépendance à l'ordre de clic.
Corrigez d'abord ça. Un notebook qui ne passe pas le Restart & Run All ne peut pas être porté en script : vous porteriez un comportement que vous ne connaissez pas.
Étape 2 : sortir les constantes en tête de fichier
Tout ce qui est une décision, pas un calcul, remonte en haut :
# config.py
from pathlib import Path
DATA_DIR = Path(__file__).parent.parent / "data"
FICHIER_VENTES = DATA_DIR / "ventes_2026.csv"
DATE_DEBUT = "2026-01-01"
SEUIL_OUTLIER = 3.0
COLONNES_ATTENDUES = ["date", "region", "produit", "quantite", "prix_unitaire"]
Deux effets immédiats : on change un paramètre sans relire le code, et on voit d'un coup d'oeil ce dont le script dépend.
Path(__file__).parent remplace le chemin absolu. C'est la ligne qui fait le plus de différence entre un script portable et un script personnel.
Étape 3 : transformer chaque bloc en fonction
Une cellule qui fait une chose = une fonction qui fait une chose.
Avant, dans le notebook :
df = pd.read_csv("C:/Users/gael/data/ventes_2026.csv")
df["date"] = pd.to_datetime(df["date"])
df = df[df["quantite"] > 0]
df["ca"] = df["quantite"] * df["prix_unitaire"]
Après :
def charger_ventes(chemin: Path) -> pd.DataFrame:
"""Charge le fichier de ventes brut et type la colonne date."""
df = pd.read_csv(chemin)
df["date"] = pd.to_datetime(df["date"])
return df
def nettoyer_ventes(df: pd.DataFrame) -> pd.DataFrame:
"""Retire les lignes de quantite nulle ou negative."""
return df[df["quantite"] > 0].copy()
def calculer_ca(df: pd.DataFrame) -> pd.DataFrame:
"""Recalcule le CA a partir des quantites, jamais depuis une colonne fournie."""
df = df.copy()
df["ca"] = df["quantite"] * df["prix_unitaire"]
return df
Trois règles suffisent :
- Une fonction prend des données en entrée et retourne des données. Pas de lecture de variable globale, pas d'écriture dans une globale.
.copy()avant toute modification. Vous évitez leSettingWithCopyWarninget les effets de bord silencieux.- Une docstring d'une ligne. Si vous n'arrivez pas à l'écrire, la fonction fait deux choses : coupez-la en deux.
Le typage (df: pd.DataFrame -> pd.DataFrame) n'est pas décoratif : c'est ce qui permet à votre IDE et à un relecteur de comprendre la chaîne sans exécuter le code.
Étape 4 : ajouter un point d'entrée explicite
# main.py
import logging
from config import FICHIER_VENTES
from pipeline import charger_ventes, nettoyer_ventes, calculer_ca
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger(__name__)
def main() -> None:
logger.info("Chargement de %s", FICHIER_VENTES)
df = charger_ventes(FICHIER_VENTES)
logger.info("%d lignes chargees", len(df))
df = nettoyer_ventes(df)
df = calculer_ca(df)
logger.info("CA total : %.2f", df["ca"].sum())
df.to_parquet("sortie/ventes_propres.parquet", index=False)
if __name__ == "__main__":
main()
Le if __name__ == "__main__": n'est pas un rituel : il rend le module importable depuis un test sans déclencher le traitement complet. Sans lui, votre première tentative d'écrire un test lancera un pipeline de 4 minutes.
Le logging remplace les print(). Différence pratique : vous avez l'heure, le niveau, et vous pouvez tout couper en changeant une ligne le jour où le script tourne dans un cron.
Étape 5 : déclarer les dépendances
Un script sans dépendances déclarées n'est pas exécutable ailleurs, point.
uv init
uv add pandas pyarrow
uv run python main.py
uv génère un pyproject.toml et un uv.lock. Le lock est la partie qui compte : il fige les versions exactes, donc un collègue qui clone obtient votre environnement, pas une approximation.
Si votre équipe est encore sur pip, pip freeze > requirements.txt fait le minimum syndical, mais fige aussi vos dépendances de dépendances de manière illisible.
Étape 6 : verrouiller le comportement avec 3 tests
Pas une suite de tests exhaustive. Trois tests, sur les trois fonctions qui feraient le plus mal en cas de régression silencieuse :
# test_pipeline.py
import pandas as pd
from pandas.testing import assert_frame_equal
from pipeline import nettoyer_ventes, calculer_ca
def test_nettoyer_retire_les_quantites_negatives():
df = pd.DataFrame({"quantite": [5, -2, 0, 3]})
assert len(nettoyer_ventes(df)) == 2
def test_calculer_ca_ignore_la_colonne_ca_fournie():
df = pd.DataFrame({"quantite": [2], "prix_unitaire": [10.0], "ca": [999.0]})
assert calculer_ca(df)["ca"].iloc[0] == 20.0
Le second test est le plus utile de tous : il documente une règle métier (le CA se recalcule, on ne fait pas confiance à la colonne fournie) et il échouera le jour où quelqu'un « optimisera » la fonction. Le détail de la méthode est dans le guide sur tester du code pandas avec pytest.
Le résultat, en une arborescence
mon-projet/
├── pyproject.toml
├── uv.lock
├── src/
│ ├── config.py
│ ├── pipeline.py
│ └── main.py
├── tests/
│ └── test_pipeline.py
└── data/
└── .gitkeep
C'est la structure minimale. Si vous voulez le détail de chaque dossier et les erreurs de découpage classiques, voyez le guide sur comment structurer un projet Python data.
« Je suis analyste, pas développeur »
L'objection est légitime, et la réponse tient en une observation : aucune des 6 étapes ci-dessus n'est du développement logiciel. Il n'y a pas d'architecture, pas de design pattern, pas de classe. Ce sont des gestes d'hygiène, du même ordre que nommer ses colonnes correctement.
Ce qui change, c'est le retour : un script structuré se relance dans 6 mois, se transmet, se corrige en 10 minutes au lieu d'une demi-journée. Et surtout, il devient relisable par quelqu'un d'autre, ce qui est la condition pour qu'on vous confie des sujets plus gros.
Par où commencer concrètement
- Prenez votre notebook le plus utilisé, pas le plus gros.
- Faites le Restart & Run All. Corrigez.
- Appliquez les étapes 2 à 4 en une session de 2 heures.
- Les tests et
uvviendront au deuxième passage.
Si vous voulez d'abord savoir où vous en êtes, le quiz code production-ready donne un score sur 100 en 15 questions et identifie le maillon faible de votre pratique actuelle. Et si le blocage se situe plutôt côté versioning, le cours interactif gratuit Git pour data analyst fait passer les scénarios d'équipe en pratique, tandis que le guide des 8 situations Git qui font vraiment mal donne les commandes de sortie de secours.
Dernier point si vous générez une partie de votre code avec une IA : le découpage en fonctions courtes est aussi ce qui rend ce code vérifiable morceau par morceau, avec les 7 contrôles à passer avant qu'un chiffre généré par IA soit livré.
Pour la chaîne complète, du notebook cassé jusqu'à la CI verte sur un fil rouge d'entreprise unique, c'est ce que couvre la formation DataCraft.
Questions fréquentes
Faut-il abandonner les notebooks ? Non. Le notebook reste l'outil d'exploration. Ce qui pose problème, c'est de livrer un livrable récurrent sous forme de notebook. La règle simple : exploration en notebook, récurrent en script.
Combien de temps prend la conversion d'un notebook ? Entre 2 et 4 heures pour un notebook de 300 lignes, la première fois. Une heure ensuite, une fois les gestes automatisés.
Faut-il tout convertir d'un coup ? Non. Convertissez le notebook qui vous coûte le plus de temps en maintenance. Le gain est immédiat et sert de démonstration au reste de l'équipe.

Approfondir avec mon livre
"Business Intelligence avec Python" - Le guide complet pour maîtriser l'analyse de données
Voir sur Amazon →