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 dePYTHONPATH
; - 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.
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 depuismodule2
,module2
importe depuismodule3
,module3
importe depuismodule1
.
. . .
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)