Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

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_module

Rappel 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_simple
Chargement 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 à :

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

On importe un package exactement comme un module :

import package_jouet
chargement 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)
module
type(package_jouet.module_jouet)
module

Ainsi, 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.spam
The 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.