Files
training.python.beginner/documentation/99-more-imports.md
2025-07-04 19:26:39 +02:00

6.5 KiB

title, author
title author
Les imports Steve Kossouho

Plus d'imports


Packages et modules

Nous avons vu comment créer packages et modules dans le tronc principal de la formation, mais ça vaut toujours le coup d'en préciser un peu plus sur le concept. Pour rappel :

  • Module : Un fichier Python contenant (ou pas) du code.
  • Package : Un répertoire contenant un fichier Python portant le nom __init__.py

L'intérêt du concept de package pour les développeurs est de pouvoir ranger du code en arborescence.


Du point de vue de l'interpréteur Python, les modules et packages sont tous des objets de type module : ce sont des objets qui contiennent du code. La différence se trouve au niveau du développeur :

  • Les packages auront leur code représenté dans leur fichier __init__.py,
  • Les modules étant des fichiers, leur code se trouve directement à l'intérieur,

(1/2)


  • Les packages peuvent contenir d'autres packages et modules, et permettent d'organiser du code dans une arborescence,
  • Tous les modules et packages à l'intérieur d'un package sont accessibles comme des attributs de l'objet représentant le package, en plus de ce qui est défini dans le fichier __init__.py.

(2/2)


L'import de modules en mémoire

Lorsque vous faites un import en Python, l'interpréteur va chercher dans la bibliothèque standard (et ailleurs si possible) ce que vous lui demandez d'importer. L'outil se comporte d'une façon similaire à la suivante :

from csv import DictReader
reader = DictReader([])

  • Python cherche un module/package csv dans le répertoire courant;
  • Python cherche le module/package csv dans les dossiers de PYTHONPATH;
  • Dans le répertoire site-packages de l'environnement virtuel,
  • Puis la cherche dans la bibliothèque standard, etc.

Si trouvé, csv est ajouté à un index interne des éléments déjà chargés en mémoire. Pour importer quoi que ce soit depuis le module, le code entier du module devra être exécuté. L'interpréteur ne donne ensuite accès qu'à DictReader au script qui l'a demandé.


Chargement et optimisation

Lorsque l'interpréteur charge un module en mémoire, il garde une référence du module chargé (avec son chemin), de façon à ce que si lors de l'exécution de votre programme vous venez à exécuter à nouveau un import du même module, l'interpréteur optimise en ignorant votre commande : le module et son contenu sont déjà connus et en mémoire.

Et heureusement : Si 20 modules de votre projet importaient le même module, beaucoup d'énergie et de temps serait perdus sans ce mécanisme.


Le PYTHONPATH

Ce qu'on appelle le PYTHONPATH, est une variable d'environnement qui vous permet d'indiquer à un interpréteur Python lancé en ligne de commande, que les imports peuvent être trouvés relativement à une liste de répertoires que vous pouvez indiquer. Dans un terminal bash :

export PYTHONPATH=/mon/repertoire:$PYTHONPATH

La commande est valide pendant l'exécution du terminal en cours.


Le PYTHONPATH dans PyCharm

PyCharm est capable de configurer automatiquement pour vous le PYTHONPATH juste avant d'exécuter vos scripts. Cela permet d'écrire des imports relatifs à l'arborescence de votre projet sans erreur.

Dialogue de configuration de lancement de scripts


Cette boîte de dialogue contient deux cases à cocher, pour ajouter au PYTHONPATH vos dossiers de projet marqués comme racines des sources ou de contenu. Pour marquer dans l'IDE un dossier comme étant un dossier racine des sources, il suffit de se rendre dans l'arborescence du projet, cliquer droit sur un répertoire, choisir Mark directory as, puis Sources root.


Les imports relatifs

Lorsque vous faites des imports en Python, le langage vous offre une notation vous permettant d'effectuer des imports dits relatifs. Le nom indique que depuis un module ou un package, vous pouvez importer un module qui se trouve à un emplacement relatif :

- module_a.py
- module_b.py
- package_1/
  - submodule_1a.py

Si depuis module_a, je souhaite importer depuis module_b, je peux écrire par exemple (notez le point) :

from .module_b import symbol

Ce type d'import est pratique, mais provoquera une erreur dans un des cas suivants :

  • Votre module ne se trouve pas dans un package (son chemin ne contient pas de point),
  • Votre module est votre point d'entrée (vous l'avez exécuté spécifiquement), donc son nom devient __main__ et ne fait donc pas partie d'un package

On peut aussi importer un module qui se trouve dans un répertoire parent. En partant de submodule_1a, on peut écrire (notez les deux points) :

from ..module_b import function1
from ...module_top import functiona

Plus je souhaite remonter dans l'arborescence, plus je peux ajouter de points à mon import (même si c'est déconseillé).


Les imports circulaires

Lorsque l'on écrit des projets complexes, il peut arriver au bout d'un moment que notre code finisse par effectuer les actions suivantes :

. . .

  • module1 importe depuis module2,
  • module2 importe depuis module3,
  • module3 importe depuis module1.

. . .

Ici, module1 importe indirectement module3 et module3 importe module1. Ceci est un import circulaire.


Dans un tel cas de figure, l'import est impossible car l'import depuis module3 nécessite que module1 ait fini d'être chargé... or module1 ne peut se charger que si module2 et indirectement module3 ont été chargés.

Lorsque cela se produit, votre structure doit être modifiée, soit en déplaçant une partie du code qui serait écrite dans module1 et utilisée par module3, soit en déplaçant une partie de module2 et module3 dans module1 (moins recommandable).


J'écris quoi, package ou module ?

Vous pouvez vous demander, puisqu'au niveau de l'interpréteur, package ou module ne font aucune différence, si dans certains cas, c'est raisonnable de créer un package à la place d'un module.

La règle générale peut se découper ainsi pour savoir si vous devriez utiliser un package :

  • Le nom du module donne une indication sur le périmètre de son code,
  • Si ce périmètre vous semble large (notamment en fonctionnalités et lignes de code),
  • Si vous pensez que ce périmètre peut être découpé en sous-catégories,
  • Alors vous devriez probablement utiliser un package. (et un module sinon)