Le PYTHON PATH

Le mécanisme d'import en Python est simple à utiliser, voire même ludique. Mais quand arrive le moment de développer un projet d'envergure, plus rien ne fonctionne. À de nombreuses reprises dans l'histoire de Python ses concepteurs ont fait des choix techniques différents et le mécanisme d'import ne fait pas exception. Un peu déroutant au départ, tout rentre dans l'ordre une fois qu'on a compris le fonctionnement du PYTHON PATH, clé de voute du système d'import de Python.

Tout au long de cet article, nous supposerons l'existence d'un projet demo organisé de la manière suivante :

demo/                   <-- répertoire du projet
├── pckg_A/                 <-- sous-répertoire
│   ├── apple.py
│   └── apricot.py
├── pckg_B/                 <-- sous-répertoire
│   ├── banana.py
│   └── blueberries.py
├── mytest                  <-- sous-répertoire
│   └── testing.py
└── main.py

Un peu de vocabulaire en passant :

  • Les répertoires pckg_A, pckg_B et mytest sont appelés des packages.
  • Les fichiers main.py, apple.py, apricot.py, banana.py, blueberries.py et testing.py sont appelés des modules.

Particularité, en Python tous les packages sont des modules mais tous les modules ne sont pas des packages (cf. la documentation). Autrement dit, les packages sont des modules spéciaux. On peut donc importer des packages de la même manière qu'on importe des modules.

Fonctionnement du PYTHON PATH

Lorsque l'instruction import module est invoquée, l'interpréteur Python va :

  1. Rechercher le module module en explorant une liste prédéfinie de répertoires (c'est cette liste qu'on appelle le PYTHON PATH)
  2. Charger le code du module et le mettre à disposition dans une variable du même nom

Bien qu'étant la moins complexe des deux, l'étape n°1 est celle qui pose le plus de problèmes aux nouveaux développeurs Python. Détaillons donc son fonctionnement.

Commençons par observer le contenu du PYTHON PATH lorsqu'on exécute le fichier demo/main.py :

# $> cat demo/main.py
import sys
print(sys.path)  # cette ligne permet d'afficher le PYTHON PATH
# $> python demo/main.py
[
    '/home/bdelmee/working-directory/demo',
    '/home/bdelmee/miniconda3/lib/python37.zip',
    '/home/bdelmee/miniconda3/lib/python3.7',
    '/home/bdelmee/miniconda3/lib/python3.7/lib-dynload',
    '/home/bdelmee/working-directory/demo/venv/lib/python3.7/site-packages'
]
  • La première ligne correspond au répertoire qui contient le point d'entrée du programme (dans cet exemple le fichier main.py). Il ne correspond pas au répertoire courant ($PWD) du terminal depuis lequel la commande Python est lancée.
  • Les trois lignes suivantes correspondent aux répertoires où sont stockés les modules de la librairie standard. On peut voir ici que la librairie installée par miniconda est utilisée à la place de celle fournie par la distribution Linux (sans impact pour le reste des explications).
  • La dernière ligne correspond aux sites-packages. C'est ici que sont stockés les modules installés via pip. À titre d'information, c'est ce répertoire que modifie venv pour créer des environnements virtuels.

Il est possible d'importer les modules présents dans le répertoir demo puisque ce dernier est inclus dans le PYTHON PATH :

# $> cat demo/main.py
from pckg_A import apple    # pckg_A est un sous-module de demo -> OK
from pckg_B import banana   # pckg_B est un sous-module de demo -> OK
from mytest import testing  # mytest est un sous-module de demo -> OK

Observons maintenant le contenu de notre PYTHON PATH lorsqu'on exécute le fichier demo/mytest/testing.py :

# $> cat demo/mytest/testing.py
import sys
print(sys.path)
# $> python demo/test/testing.py
[
    '/home/bdelmee/working-directory/demo/mytest',
    '/home/bdelmee/miniconda3/lib/python37.zip',
    '/home/bdelmee/miniconda3/lib/python3.7',
    '/home/bdelmee/miniconda3/lib/python3.7/lib-dynload',
    '/home/bdelmee/working-directory/demo/venv/lib/python3.7/site-packages'
]
  • On remarque que la première ligne a changé, le répertoire demo a été remplacé par le répertoire demo/mytest. En effet le point d'entrée du programme n'est plus demo/main.py mais demo/mytest/testing.py, d'où la mise à jour du PYTHON PATH. C'est ce comportement qui déroute le plus les nouveaux développeurs Python. Le PYTHON PATH dépend (en partie) du point d'entrée du programme.

La conséquence indésirée de ce changement, c'est que les modules présents dans les répertoires demo/pckg_A et demo/pckg_B ne sont plus accessibles. Lorsqu'on essaie de les importer :

# $> cat demo/mytest/testing.py
import testing              # testing est un sous-module de test -> OK
from pckg_A import apple    # pckg_A n'est pas un sous-module de pckg_C -> Erreur
from pckg_B import banana   # pckg_B n'est pas un sous-module de pckg_C -> Erreur
# $> python demo/mytest/testing.py
ModuleNotFoundError: No module named 'pckg_A'

Heureusement ce problème n'est pas définitif, voyons maintenant comment importer les modules pckg_A et pckg_B depuis le module mytest.

Méthode n°1 : Modifier le PYTHON PATH

Une manière de résoudre ce problème d'import est de modifier le PYTHON PATH en ajoutant le répertoire demo. Les modules pckg_A et pckg_B seront de nouveaux accessibles depuis le module mytest (et aussi depuis n'importe quel autre emplacement du programme).

On peut ajouter des répertoires au PYTHON PATH en modifiant la variable d'environnement $PYTHONPATH. Dans l'exemple, cela donnerait :

$> export PYTHONPATH="/home/bdelmee/working-directory/demo"

On peut aussi modifier le PYTHON PATH directement depuis python. Cela a l'avantage de ne pas nécessiter d'intervention côté système. Dans l'exemple, cela donnerait :

# $> cat demo/mytest/testing.py
import sys
sys.path.append("/home/bdelmee/working-directory/demo")

from pckg_A import apple  # OK

Méthode n°2 : Utiliser un point d'entrée unique

Une autre méthode consiste à s'assurer que le PYTHON PATH inclut toujours le répertoire demo en utilisant un script unique comme point d'entrée du programme. En plaçant ce script à la racine du projet, le répertoire racine sera automatiquement placé dans le PYTHON PATH et les sous-modules seront alors accessibles. Dans notre exemple, ce rôle pourrait être joué par le fichier main.py.

À retenir :

  • Vous ne pouvez pas importer un module s'il ne se trouve pas dans l'un des répertoires/sous-répertoires du PYTHON PATH.
  • Le répertoire contenant le point d'entrée du programme est automatiquement ajouté au PYTHON PATH.
  • Il est possible de modifier le PYTHON PAHT via la variable d'environnement $PYTHONPATH ou directement dans le code Python via sys.path