Résoudre les erreurs d'importation relative en Python

Récemment, lors du développement d'un projet Python, j'ai rencontré l'erreur suivante : ImportError: attempted relative import with no known parent package. Le scénario était le suivant : je voulais utiliser une fonction définie dans script_a.py à partir de script_b.py en utilisant une syntaxe comme from ..dossier_a.script_a import ma_fonction, mais l'exécution de script_b.py générait systématiquement cette erreur.

projet
├── dossier_a
│   └── script_a.py
├── dossier_b
│   └── script_b.py
└── lanceur_principal.py

Solutions rapides

Pour importer correctement script_a.py dans script_b.py, plusieurs approches existent :

  • Modifier l'architecture du projet pour exécuter depuis lanceur_principal.py
  • Ajouter le répertoire racine au chemin de recherche Python avec sys.path.append() et utiliser from dossier_a.script_a import ma_fonction
  • Exécuter avec l'option -m : python -m projet.dossier_b.script_b [**Solution recommandée** 👍]

Analyse technique approfondie

Une compréhension superficielle du système d'importation Python peut mener à deux erreurs fréquentes :

  • ModuleNotFoundError: Aucun module nommé 'xxx' trouvé
  • ImportError: Tnetative d'importation relative sans package parent connu

Il existe fondamentalement deux méthodes d'importation :

  1. Importation absolue : from module import element - recherche dans les modules connus
  2. Importation relative : from .module import element - basée sur le chemin relatif au fichier actuel

Méthode 1 : Importation absolue et gestion du chemin

L'interpréteur Python recherche les modules dans plusieurs emplacements séquentiels :

  1. Le répertoire du script en cours d'exécution
  2. Les répertoires définis dans la variable d'environnement PYTHONPATH
  3. Les répertoires d'installation standard de Python
import sys
print(sys.path)
# ['/chemin/actuel', '/usr/lib/python310.zip', '/usr/lib/python3.10', ...]

Pour résoudre notre problème d'importation entre dossiers, nous pouvons ajouter manuellement le répertoire racine au chemin de recherche :

import sys
import os

# Ajout du chemin racine (approche moins élégante mais fonctionnelle)
chemin_racine = os.path.dirname(os.path.abspath(__file__))
chemin_parent = os.path.dirname(chemin_racine)
sys.path.insert(0, chemin_parent)

from dossier_a.script_a import ma_fonction

Une approche plus robuste peut utiliser cette condition :

if __package__:
    from ..dossier_a import script_a
else:
    sys.path.append(os.path.dirname(os.path.dirname(__file__)))
    import script_a

Méthode 2 : Importation relative dans un package

Cette méthode ne fonctionne qu'à l'intérieur d'un package reconnu par Python. Lorsqu'un script est exécuté directement avec python script.py, Python ne le considère pas comme faisant partie d'un package, même si un fichier __init__.py est présent. Vous pouvez vérifier cela avec print(__package__) - le résultat sera None pour un script exécuté directement.

Contrairement à une idée répandue, l'ajout de fichiers __init__.py ne résout pas systématiquement ce problème particulier.

Pour utiliser les imporattions relatives entre script_a.py et script_b.py, il faut exécuter le module comme un package :

python -m projet.dossier_b.script_b

Quelques points importants :

  • Le projet est traité comme un package complet
  • L'extension .py est omise
  • La commande python -m dossier_b.script_b ne fonctionnerait pas (limitée au top-level package)

Pour illustrer avec une structure plus complexe :

super_projet
├── projet
│   ├── dossier_a
│   │   └── script_a.py
│   ├── dossier_b
│   │   └── script_b.py
│   └── lanceur_interne.py
└── lanceur_externe.py

  • Depuis lanceur_externe.py : from projet.dossier_b import script_b permet les imports relatifs avec from ..dossier_a.script_a import ma_fonction
  • Depuis lanceur_interne.py : import dossier_b.script_b ne permet que les imports relatifs simples (from .module import)

Notes complémentaires

Remarques importantes :

  • Cet article présente des solutions testées et fonctionnelles, d'autres approches valides peuvent exister
  • L'utilisation d'imports absolus comme from dossier_a import script_a est également possible dans certains contextes
  • Les imports relatifs sont généralement préférés pour la maintenance du code à l'intérieur d'un package

Références techniques additionnelles :

  • Documentation officielle Python sur le module search path
  • Documentation officielle Python sur les packages
  • Discussions sur Stack Overflow concernent les imports relatifs

Étiquettes: Python import-error module-import relative-import sys.path

Publié le 17 septembre à 19h14