Complément - niveau basique¶
Dans ce complément, nous approfondissons la notion de module, qui a été introduite dans les vidéos, et nous décrivons la notion de package qui permet de créer des bibliothèques plus structurées qu’avec un simple module.
Pour ce notebook nous aurons besoin de deux utilitaires pour voir le code correspondant aux modules et packages que nous manipulons :
from modtools import show_moduleRappel sur les modules¶
Nous avons vu dans la vidéo qu’on peut charger une bibliothèque, lorsqu’elle se présente sous la forme d’un seul fichier source, au travers d’un objet python de type module.
Chargeons un module “jouet” :
import module_simpleChargement du module module_simple
Voyons à quoi ressemble ce module :
show_module(module_simple)Fichier /__w/course/course/modules/module_simple.py
----------------------------------------
1|print("Chargement du module", __name__)
2|
3|def spam(n):
4| "Le polynôme (n+1)*(n-3)"
5| return n**2 - 2*n - 3
On a bien compris maintenant que le module joue le rôle d’espace de nom, dans le sens où :
# on peut définir sans risque une variable globale 'spam'
spam = 'eggs'
print("spam globale", spam)spam globale eggs
# qui est indépendante de celle définie dans le module
print("spam du module", module_simple.spam)spam du module <function spam at 0x7f8664e48cc0>
Pour résumer, un module est donc un objet python qui correspond à la fois à :
un (seul) fichier sur le disque ;
et un espace de nom pour les variables du programme.
La notion de package¶
Lorsqu’il s’agit d’implémenter une très grosse bibliothèque, il n’est pas concevable de tout concentrer en un seul fichier. C’est là qu’intervient la notion de package, qui est un peu aux répertoires ce que que le module est aux fichiers.
Nous allons illustrer ceci en créant un package qui contient un module. Pour cela nous créons une arborescence de fichiers comme ceci :
package_jouet/
__init__.py
module_jouet.pyOn importe un package exactement comme un module :
import package_jouetchargement du package package_jouet
Chargement du module package_jouet.module_jouet dans le package 'package_jouet'
Voici le contenu de ces deux fichiers :
show_module(package_jouet)Fichier /__w/course/course/modules/package_jouet/__init__.py
----------------------------------------
1|print("chargement du package", __name__)
2|
3|spam = ['a', 'b', 'c']
4|
5|# on peut forcer l'import de modules
6|import package_jouet.module_jouet
7|
8|# et définir des raccourcis
9|jouet = package_jouet.module_jouet.jouet
show_module(package_jouet.module_jouet)Fichier /__w/course/course/modules/package_jouet/module_jouet.py
----------------------------------------
1|print("Chargement du module", __name__, "dans le package 'package_jouet'")
2|
3|jouet = 'une variable définie dans package_jouet.module_jouet'
Comme on le voit, le package porte le même nom que le répertoire, c’est-à-dire que, de même que le module module_simple correspond au fichier module_simple.py, le package python package_jouet corrrespond au répertoire package_jouet.
Note historique par le passé, pour définir un package, il fallait obligatoirement créer dans le répertoire (celui, donc, que l’on veut exposer à python), un fichier nommé __init__.py; ce n’est plus le cas depuis Python-3.3.
Comme on le voit, importer un package revient essentiellement à charger, lorsqu’il existe, le fichier __init__.py dans le répertoire correspondant (et sinon, on obtient un package vide).
On a coutume de faire la différence entre package et module, mais en termes d’implémentation les deux objets sont en fait de même nature, ce sont des modules :
type(package_jouet)moduletype(package_jouet.module_jouet)moduleAinsi, le package se présente aussi comme un espace de nom, à présent on a une troisième variable spam qui est encore différente des deux autres :
package_jouet.spamThe history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.['a', 'b', 'c']
L’espace de noms du package permet de référencer les packages ou modules qu’il contient, comme on l’a vu ci-dessus, le package référence le module au travers de son attribut module_jouet :
package_jouet.module_jouet<module 'package_jouet.module_jouet' from '/__w/course/course/modules/package_jouet/module_jouet.py'>À quoi sert __init__.py ?¶
Vous remarquerez que le module module_jouet a été chargé au même moment que package_jouet. Ce comportement n’est pas implicite. C’est nous qui avons explicitement choisi d’importer le module dans le package (dans __init__.py).
Cette technique correpond à un usage assez fréquent, où on veut exposer directement dans l’espace de nom du package des symboles qui sont en réalité définis dans un module.
Avec le code ci-dessus, après avoir importé package_jouet, nous pouvons utiliser
package_jouet.jouet'une variable définie dans package_jouet.module_jouet'alors qu’en fait il faudrait écrire en toute rigueur
package_jouet.module_jouet.jouet'une variable définie dans package_jouet.module_jouet'Mais cela impose alors à l’utilisateur d’avoir une connaissance sur l’organisation interne de la bibliothèque, ce qui est considéré comme une mauvaise pratique.
D’abord, cela donne facilement des noms à rallonge et du coup nuit à la lisibilité, ce n’est pas pratique. Mais surtout, que se passerait-il alors si le développeur du package voulait renommer des modules à l’intérieur de la bibliothèque ? On ne veut pas que ce genre de décision ait un impact sur les utilisateurs.
De manière générale, __init__.py peut contenir n’importe quel code Python chargé d’initialiser le package.
Notez que depuis Python-3.3, **la présence de __init__.py n’est plus strictement nécessaire.
Lorsqu’il est présent, comme pour les modules usuels, __init__.py n’est chargé qu’une seule fois par l’interpréteur Python; s’il rencontre plus tard à nouveau le même import, il l’ignore silencieusement.
Pour en savoir plus¶
Voir la section sur les modules dans la documentation python, et notamment la section sur les packages.