Un module Python est un fichier portant l’extension .py qui contient des fonctions, des classes ou des variables. Cette définition tient en une phrase, et pourtant le mécanisme qui se cache derrière le mot module soulève des questions dès qu’un projet dépasse le stade du script unique.
Le système d’import de Python a évolué ces dernières années, notamment avec l’API importlib et le modèle basé sur les objets ModuleSpec. Comprendre ces rouages évite des erreurs de structure qui deviennent coûteuses à corriger une fois le code en production.
A voir aussi : Comprendre les logs système en quelques étapes : les bases essentielles pour une interprétation rapide
Ce que Python exécute réellement lors d’un import de module
Quand l’interpréteur rencontre une instruction import, il ne se contente pas de « lire » le fichier ciblé. Il lance une séquence précise : recherche du module dans sys.modules (un cache interne), puis parcours des chemins listés dans sys.path, et enfin compilation du fichier .py en bytecode .pyc si nécessaire.
Depuis Python 3.11 et 3.12, la documentation officielle oriente les développeurs vers importlib et l’objet ModuleSpec pour inspecter ou personnaliser ce processus. Les anciennes manipulations directes de __file__ ou de pkg_resources sont considérées comme plus fragiles. L’API importlib.resources remplace pkg_resources pour accéder aux données d’un module.
A voir aussi : Installer pip en offline : utiliser install py-pip sans connexion internet
Un point souvent mal compris : un module n’est exécuté qu’une seule fois par session Python. Le résultat est mis en cache dans sys.modules. Si vous modifiez le fichier source après le premier import, l’interpréteur continue d’utiliser la version en mémoire, sauf rechargement explicite via importlib.reload().

Différence entre module, package et namespace package en Python
La confusion entre ces trois termes revient fréquemment. Un module est un fichier .py. Un package est un dossier contenant un fichier __init__.py, ce qui permet de regrouper plusieurs modules sous un même espace de noms.
Depuis Python 3.3 (PEP 420), il existe une troisième catégorie que la plupart des tutoriels passent sous silence : les namespace packages implicites. Un dossier sans __init__.py peut former un package, à condition que ses sous-répertoires soient répartis sur plusieurs emplacements du sys.path. Ce mécanisme sert principalement aux grandes bibliothèques distribuées en plusieurs parties indépendantes sur PyPI.
En pratique, pour un projet standard, la présence d’un __init__.py reste la norme. Ce fichier peut être vide ou contenir du code d’initialisation (imports groupés, configuration de variables de package). Sa suppression involontaire provoque des erreurs d’import silencieuses, car Python traite alors le dossier comme un namespace package au lieu d’un package classique.
Structure type d’un package avec init
Un dossier geometry/ contenant __init__.py, operations.py et shapes.py forme un package. Le fichier __init__.py peut exposer certaines fonctions pour simplifier l’import côté utilisateur :
from geometry import calcul_airefonctionne si__init__.pyimporte explicitementcalcul_airedepuisoperations.pyfrom geometry.operations import calcul_airefonctionne toujours, indépendamment du contenu de__init__.pyfrom geometry import *n’importe que les noms listés dans la variable__all__du fichier__init__.py, si elle est définie
Pourquoi from import * pose un vrai problème de maintenance
L’instruction from module import * importe dans l’espace de noms courant tous les noms publics du module cible. Cette commodité apparente génère deux risques concrets.
Le premier : les collisions de noms. Si deux modules définissent une fonction convert() et que vous importez les deux avec *, la seconde écrase la première sans avertissement. Le bug qui en résulte peut mettre des heures à identifier.
Le second : la fragilité face aux mises à jour. Quand l’auteur d’une bibliothèque ajoute un nouveau nom public dans son module, votre code l’importe automatiquement. Une mise à jour mineure peut introduire une collision de noms dans votre projet. La documentation Python et la PEP 8 déconseillent cette syntaxe, en recommandant de réserver __all__ aux packages publics stables destinés à être versionnés sur PyPI.

Contrôler l’exécution d’un module Python avec __name__
Tout fichier Python possède un attribut __name__. Quand le fichier est exécuté directement (comme script), cet attribut vaut "__main__". Quand il est importé comme module, __name__ prend le nom du fichier sans extension.
Le bloc if __name__ == "__main__": exploite cette distinction. Le code placé dans ce bloc ne s’exécute que si le fichier est lancé directement, pas lors d’un import. Ce mécanisme sert à deux choses : placer des tests rapides pendant le développement, et permettre à un même fichier de fonctionner à la fois comme module importable et comme script autonome.
Piège fréquent avec les imports relatifs
Un fichier exécuté comme script (python mon_fichier.py) ne peut pas utiliser d’imports relatifs (syntaxe from .module import fonction). Python considère qu’un script lancé directement n’appartient à aucun package. Pour contourner cette limitation, il faut exécuter le module via la syntaxe python -m mon_package.mon_fichier, qui préserve le contexte de package.
Les retours terrain divergent sur ce point : certains développeurs structurent leurs projets exclusivement avec des imports absolus pour éviter toute ambiguïté, tandis que d’autres préfèrent les imports relatifs pour leur portabilité lors de renommages de packages.
Variables et fonctions d’un module : ce qui est exposé et ce qui ne l’est pas
Par convention, un nom préfixé par un underscore (_ma_variable) est considéré comme privé. Python n’interdit pas son import, mais les outils de linting (pylint, flake8) signalent son utilisation depuis un autre module.
La variable __all__, définie au niveau du module, contrôle explicitement la liste des noms exportés par from module import *. Elle ne modifie pas le comportement de import module ni de from module import nom_explicite.
__all__ = ["fonction_a", "ClasseB"]limite l’export aux noms listés lors d’un import avec*- Les fonctions, classes et variables non listées restent accessibles par import explicite
- Omettre
__all__dans un module expose tous les noms ne commençant pas par un underscore lors d’unimport *
La gestion de __all__ demande une maintenance rigoureuse. Chaque ajout de fonction publique dans le module nécessite une mise à jour de cette liste, sous peine d’incohérence entre l’API documentée et l’API réellement accessible.
Le système de modules Python repose sur des mécanismes simples en surface (import, fichiers .py, dossiers avec __init__.py) mais dont les implications se révèlent à mesure qu’un projet grandit. Maîtriser le rôle de sys.path, la portée de __all__ et le fonctionnement du cache sys.modules constitue le socle technique pour structurer un code Python maintenable sur la durée.

