Un fichier Python avec l’extension .py devient un module dès qu’un autre fichier tente de l’importer. Cette mécanique, simple en apparence, génère des erreurs spécifiques que les guides sur les exceptions ou la syntaxe n’abordent pas. L’enjeu n’est pas de corriger un SyntaxError classique, mais de comprendre pourquoi un module que l’on vient d’écrire refuse de s’importer, s’exécute deux fois ou entre en conflit avec un paquet installé.
ModuleNotFoundError et structure de fichiers py module
L’erreur la plus déroutante pour un premier module survient quand le code fonctionne en exécution directe (python mon_module.py) mais échoue à l’import depuis un autre fichier. Le message ModuleNotFoundError: No module named 'mon_module' apparaît alors que le fichier existe bien dans le dossier.
La cause tient à la manière dont Python construit son chemin de recherche (sys.path). Quand un script est lancé directement, Python ajoute le répertoire courant au chemin. Quand un import est déclenché depuis un autre emplacement, ce répertoire n’est plus dans sys.path.
Le placement du fichier par rapport au point d’entrée détermine la réussite de l’import. Deux réflexes corrigent la majorité des cas :
- Placer le module dans un sous-dossier dédié (un paquet avec un fichier
__init__.py) plutôt qu’à la racine du projet, ce qui rend les imports prévisibles quel que soit l’endroit d’exécution. - Vérifier l’interpréteur actif avec
import sys; print(sys.executable)pour s’assurer que le terminal ou le notebook utilise bien l’environnement où le module est accessible. - Éviter de nommer le fichier comme un module standard de Python (par exemple
random.pyouemail.py), ce qui masque la bibliothèque native et provoque des imports circulaires involontaires.

Layout src/ et pyproject.toml : pourquoi l’import échoue sur un projet récent
Depuis quelques années, la communauté Python privilégie le layout src/ : le code applicatif est rangé dans src/mon_projet/ et la configuration du paquet passe par un fichier pyproject.toml unique, en remplacement de setup.py.
Ce changement de convention crée un piège concret. Un débutant qui suit un tutoriel récent structure son projet avec src/, mais tente ensuite d’importer son module directement depuis la racine. L’import échoue parce que le sous-dossier src/ n’est pas un paquet installé dans l’environnement.
Installer son propre module en mode éditable
La solution technique est d’installer le projet localement avec pip install -e . (mode éditable). Cette commande lit le pyproject.toml, enregistre le paquet dans l’environnement virtuel et rend les imports fonctionnels sans manipuler sys.path manuellement.
Sans cette étape, chaque modification du chemin de fichiers relance le même ModuleNotFoundError. Un module placé dans src/ doit être installé localement pour devenir importable.
Erreur d’import circulaire entre fichiers Python
Un import circulaire survient quand deux modules s’importent mutuellement. Le fichier a.py importe b, et b.py importe a. Python n’interdit pas cette construction, mais il charge les modules de manière séquentielle : au moment où b tente de lire un objet de a, celui-ci n’est pas encore défini.
Le résultat est un ImportError: cannot import name 'x' from 'a', alors que la fonction ou la classe existe bien dans le fichier. Le message d’erreur pointe vers un nom, pas vers la circularité, ce qui rend le diagnostic difficile.
Deux corrections applicables immédiatement
La première consiste à déplacer l’import à l’intérieur de la fonction qui en a besoin, plutôt qu’en tête de fichier. Python ne résout l’import qu’au moment de l’appel, quand les deux modules sont déjà chargés.
La seconde, plus propre, revient à extraire le code partagé dans un troisième module. Si a et b dépendent tous deux d’une même fonction def, cette fonction a sa place dans un fichier utils.py importé par les deux autres. La dépendance circulaire disparaît.
Le piège du double code avec if __name__ et les variables globales
Beaucoup de premiers modules contiennent du code exécutable en dehors de toute fonction : des appels à print, des calculs, des affectations de variables. Ce code s’exécute à chaque import, pas seulement quand le fichier est lancé directement.
Un fichier calcul.py qui contient resultat = 10 / age en dehors d’une fonction provoquera un NameError ou une ZeroDivisionError dès l’import si la variable age n’est pas définie ou vaut zéro. Le garde-fou standard est le bloc if __name__ == "__main__":, qui isole le code destiné à l’exécution directe.
Tout code exécutable hors fonction s’exécute à l’import, pas seulement au lancement du script. La règle pratique : un module ne devrait contenir que des définitions (def, class, constantes) en dehors du bloc __name__.

Conflit de noms entre module local et bibliothèque standard Python
Nommer un fichier random.py, email.py ou json.py à la racine d’un projet est une erreur que les messages d’erreur ne signalent jamais clairement. Python cherche d’abord dans le répertoire courant avant d’aller dans la bibliothèque standard. Un fichier local portant le même nom qu’un module natif le masque entièrement.
Le symptôme typique est un AttributeError : le code appelle random.randint(), mais Python charge le fichier local random.py qui ne contient pas cette fonction. Le traceback mentionne le bon nom de module, mais pointe vers le mauvais fichier.
Le diagnostic passe par une vérification rapide :
- Afficher
import random; print(random.__file__)pour voir quel fichier Python charge réellement. - Renommer le fichier local avec un nom spécifique au projet (
random_utils.py,generation_aleatoire.py). - Supprimer le fichier
__pycache__associé après le renommage, car Python peut conserver l’ancien module compilé en cache.
Ce type de conflit touche aussi les paquets installés via pip. Un dossier requests/ créé localement prendra le pas sur la bibliothèque requests. Le nom de chaque fichier py module doit être unique par rapport à la bibliothèque standard et aux paquets installés.
La majorité de ces erreurs partagent un point commun : elles ne viennent pas du code lui-même, mais de la structure du projet et des conventions de nommage. Corriger un try/except ou un raise ValueError relève de la syntaxe. Rendre un module importable de manière fiable relève de l’architecture, et c’est cette couche qui pose problème sur un premier projet Python.

