Les paquets (ou packages) forment une entité hiérarchique au-dessus des modules : un paquet est un module qui contient d’autre modules, un peu comme un dossier contient des fichiers.
D’ailleurs, les paquets prennent généralement la forme de dossiers sur le système de fichiers.
On en a déjà rencontré pendant ce cours : souvenez-vous du module xml.etree.ElementTree
: il s’agissait en fait d’un module ElementTree
dans un paquet xml.etree
.
On comprend par la même occasion qu'etree
est lui-même imbriqué dans un paquet xml
, car les paquets sont hiérarchiques.
Construction d'un paquet
Pour créer notre propre paquet, on peut alors simplement créer un nouveau répertoire dans lequel on placera nos fichiers Python (nos modules).
Créons par exemple un dossier operations
depuis le répertoire courant, avec deux fichiers addition.py
et soustraction.py
:
Nous voilà maintenant avec un nouveau paquet operations
.
Ce paquet forme un espace de noms supplémentaire autour de nos modules, et nous devons donc les préfixer de operations.
pour les importer.
>>> from operations import addition
>>> # on a importé le module addition
>>> addition.addition(1, 2)
3
>>> from operations.soustraction import soustraction
>>> # on a importé directement la fonction soustraction
>>> soustraction(1, 2)
-1
Pensez à bien vous placer depuis le répertoire contenant le dossier operations
et non dans le dossier operations
lui-même pour exécuter ce code.
Par exemple si votre dossier operations
se trouve dans un dossier projet
, il faut que vous exécutiez l’interpréteur depuis ce dossier projet
sans quoi Python ne serait pas en mesure de trouver le paquet.
On remarque que l’on ne peut pas simplement faire import operations
puis utiliser par exemple operations.addition.addition(1, 2)
comme on le ferait avec des modules.
C’est parce que les modules d’un paquet ne sont pas directement chargés quand le paquet est importé, mais nous verrons ensuite comment y parvenir.
Imports relatifs
Il peut arriver depuis un paquet que nous ayons besoin d’accéder à d’autres modules du même paquet.
Par exemple, notre fonction soustraction
pourrait vouloir faire appel à la fonction addition
.
Pour cela, on va pouvoir importer la fonction addition
dans le module soustraction
, comme on vient de le faire dans l’interpréteur interactif.
Et tout fonctionne comme prévu :
>>> from operations.soustraction import soustraction
>>> soustraction(8, 5)
3
Mais la syntaxe peut paraître lourde, pourquoi avoir besoin de préciser operations
alors qu’on est déjà dans ce paquet ?
Python a prévu une réponse à ça : les imports relatifs.
Ainsi, pour un import au sein d’un même paquet, on peut simplement référencer un autre module en le préfixant d’un .
sans indiquer explicitement le paquet (qui sera donc le paquet courant).
Et l’on peut vérifier en important le module operations.soustraction
que tout fonctionne toujours correctement.
Attention cependant, cette syntaxe d’imports relatifs n’est valable que dans le cas d’un from ... import ...
.
Il n’est ainsi pas possible d’écrire simplement import .addition
pour importer le module addition
.
En revanche la syntaxe from . import addition
est valide (équivalente à from operations import addition
).
Fichier __init__.py
Comme je le disais précédemment, le code des modules n’est pas directement chargé quand on importe le paquet.
Qu’est-ce qui se passe alors quand on fait un import operations
?
À notre niveau pas grand chose en fait. Python identifie où se trouvent les fichiers du paquet operations
et instancie un module vide qu’il nous renvoie.
Mais dans les faits, il cherche un fichier __init__.py
à l’intérieur du paquet pour l’exécuter.
C’est en fait ce fichier qui contient le code du paquet à proprement parler : tout ce qui sera présent dedans sera exécuté lors d’un import operations
.
>>> import operations
Hello
Attention au nommage du fichier, il faut bien deux underscores de part et d’autre de init
.
Bien sûr cet exemple n’est pas très utile, mais ce fichier __init__.py
peut aussi nous servir à charger directement le code des modules du paquet.
Par exemple on peut y importer nos fonctions addition
et soustraction
pour les rendre accessibles plus facilement.
>>> import operations
>>> operations.addition(3, 5)
8
>>> from operations import soustraction
>>> soustraction(8, 5)
3
Avant Python 3.3, le fichier __init__.py
était nécessaire pour que Python considère le répertoire comme un paquet.
Ce n’est plus le cas aujourd’hui mais ce fichier reste toutefois utile pour indiquer à Python que tout le code du paquet se trouve dans ce même répertoire.
Prenez ainsi l’habitude de toujours avoir un fichier __init__.py
(même vide) dans vos paquets, cela pourrait vous éviter certaines déconvenues.
Fichier __main__.py
Il existe un autre fichier « magique » au sein des paquets, le fichier __main__.py
Mais avant d’y revenir, je dois vous parler de l’option -m
de l’interpréteur Python.
C’est une option qui permet de demander à Python d’exécuter un module à partir de son nom. Cela permet de ne pas avoir à connaître le chemin complet vers le fichier du module pour le lancer. Et certains modules Python s’en servent pour mettre à disposition des petits programmes.
Par exemple le module turtle
propose une démo si on l’exécute via python -m turtle
:
Cela fonctionne aussi avec nos propres modules.
% python -m hello
Hello World!
Pour rappel, le bloc conditionnel if __name__ == '__main__'
permet de placer du code qui sera exécuté uniquement quand le module est lancé directement (python hello.py
ou python -m hello
) mais pas quand le module est importé.
Dans le cadre de notre paquet, python -m operations
cherchera à exécuter son fichier __main__.py
.
Nous pouvons alors y créer un tel fichier pour nous aussi faire une démonstration de notre paquet.
% python -m operations
Addition: 3+5 = 8
Soustraction: 8-3 = 5
En conclusion, je vous invite à consulter mon billet Notes sur les modules et packages en Python qui répond à plusieurs problématiques au sujet des paquets et des imports en Python.