La question de savoir si un projet Python doit tenir dans un seul fichier ou se distribuer en plusieurs modules Python revient régulièrement dès que la base de code grossit. Ce n’est pas une question de style : le découpage en fichiers distincts change la façon dont le code s’importe, se teste et se distribue. Avant de trancher, il vaut la peine de mesurer ce que chaque approche implique concrètement.
Fichier unique ou plusieurs modules : ce que change le découpage
Un fichier Python unique suffit pour un script court, un outil ponctuel ou un prototype. Dès que plusieurs fonctions partagent un contexte différent, ou que le fichier dépasse une taille difficile à lire d’un seul regard, la question de la séparation devient légitime.
Le tableau ci-dessous compare les deux approches sur les dimensions qui comptent en pratique.
| Critère | Fichier unique | Plusieurs modules |
|---|---|---|
| Lisibilité | Dégradée au-delà d’une certaine taille | Chaque fichier couvre un périmètre clair |
| Réutilisation | Impossible sans copier-coller | Import direct depuis un autre projet |
| Tests unitaires | Nécessite d’exécuter tout le programme | Chaque module testable indépendamment |
| Distribution (packaging) | Script autonome suffisant | Requiert pyproject.toml et un backend de build |
| Complexité d’import | Nulle | Dépend de la structure choisie (flat vs src layout) |
Le découpage ne produit pas automatiquement un code meilleur : séparer du code sans critère clair génère autant de complexité qu’un fichier trop long.
Quand séparer son code Python en modules a du sens
Trois situations justifient concrètement de passer à plusieurs fichiers.
- Des responsabilités distinctes coexistent dans le même fichier. Si un fichier contient à la fois la logique métier, l’accès aux données et l’interface utilisateur, chacun de ces blocs mérite son propre module. L’isolation facilite les modifications sans effets de bord.
- Des fonctions sont appelées depuis plusieurs endroits. Une fonction utilitaire réutilisée dans deux scripts différents n’a pas sa place dans aucun des deux : elle appartient à un module partagé, importé là où on en a besoin.
- Les tests deviennent difficiles à isoler. Quand tester une fonction oblige à exécuter l’ensemble du programme, le découpage en modules résout le problème. Un répertoire
tests/séparé du code source, comme le recommande le guide de packaging Python, suppose justement que le code importable est isolé dans sa propre structure.
Ces trois critères sont indépendants de la taille du projet. Un script de taille modeste peut déjà présenter les deux premiers, tandis qu’un fichier long peut parfaitement rester monolithique s’il remplit une seule fonction.

Flat layout ou src layout : l’impact réel sur les imports
Une fois la décision de modulariser prise, le choix de l’arborescence a des conséquences directes sur les imports et les tests.
Le flat layout place les modules à la racine du projet, à côté de setup.py ou pyproject.toml. Simple à démarrer, il pose un problème classique : Python peut importer le code source directement depuis le répertoire courant, ce qui masque les erreurs d’installation. Un test peut passer en local et échouer une fois le paquet installé.
Le src layout, documenté dans le guide officiel de packaging Python, place le code importable sous un répertoire src/, avec les tests dans un répertoire tests/ séparé. Cette organisation oblige à installer le paquet (même en mode éditable avec pip install -e .) avant de l’importer dans les tests. Elle rend la distinction entre code source et code installé explicite.
Le guide de packaging Python décrit cette structure avec un fichier pyproject.toml pour déclarer les métadonnées du projet et le backend de construction. Séparer des fichiers ne produit pas un paquet installable : il faut aussi configurer la distribution si l’objectif est de partager le code au-delà du projet local.
La compatibilité Python comme contrainte d’import
L’organisation en modules interagit avec la version de Python ciblée. Certaines fonctionnalités d’import introduites dans des versions récentes, comme les imports laziness ou certaines formes de type hints dans les annotations, ne sont pas disponibles sur des versions plus anciennes.
Un projet qui doit rester compatible avec plusieurs versions de Python doit vérifier que ses mécanismes d’import sont supportés sur l’ensemble des versions déclarées dans le champ python_requires du fichier pyproject.toml. La documentation de pylint signale d’ailleurs des avertissements spécifiques liés à l’utilisation d’imports laziness sur des versions non supportées.
Ce point est souvent ignoré lors de la modularisation initiale, puis découvert tardivement en environnement de CI ou lors d’un déploiement sur une infrastructure plus ancienne.
Ce que la modularisation ne résout pas
Découper un fichier unique en plusieurs modules ne corrige pas un code mal structuré. Si les fonctions sont trop longues, si les variables globales circulent entre les fichiers via des imports croisés, ou si les responsabilités sont floues, la modularisation déplace le problème sans le régler.
Les imports circulaires sont le symptôme le plus courant d’un découpage mal pensé : le module A importe le module B, qui importe le module A. Python gère partiellement ce cas, mais le comportement dépend de l’ordre d’exécution et devient difficile à prédire. Un découpage efficace suit les dépendances : les modules de bas niveau n’importent pas les modules de haut niveau.
La modularisation est un outil de structuration, pas de refactoring. Elle révèle la qualité de l’architecture existante plutôt qu’elle ne la crée.
Pour la plupart des projets Python qui dépassent le stade du script ponctuel, séparer le code en plusieurs modules reste pertinent – à condition de le faire selon des critères fonctionnels clairs et de configurer correctement la structure d’import choisie dès le départ.


