180 lines
6.5 KiB
Markdown
180 lines
6.5 KiB
Markdown
---
|
||
title: Les imports
|
||
author: 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 :
|
||
|
||
```python
|
||
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` :
|
||
|
||
```shell
|
||
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 :
|
||
|
||
```text
|
||
- 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) :
|
||
|
||
```python
|
||
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) :
|
||
|
||
```python
|
||
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)
|
||
|
||
----
|