---
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.

![Dialogue de configuration de lancement de scripts](assets/images/x-imports-pythonpath-pycharm.png)

----

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)

----
