2.19. Écrire des modules

Tout fichier .py est un module. Répartir un script grandissant sur quelques fichiers garde chaque fichier court et permet de partager des fonctions utilitaires communes entre les scripts.

2.19.1. Découper un script

Sortez un groupe de fonctions apparentées dans leur propre fichier :

camera_utils.py

def banner():
    print("OpenMV")

def label(text):
    return "[" + text + "]"

main.py

import camera_utils

camera_utils.banner()
print(camera_utils.label("ready"))

Sortie

OpenMV
[ready]

Les deux fichiers cohabitent dans le même répertoire. Lorsque main.py s’exécute, import camera_utils lit camera_utils.py une seule fois, exécute ses instructions de premier niveau, et lie l’objet module résultant au nom camera_utils dans main. Un second import camera_utils depuis n’importe où ailleurs renvoie le même objet – les modules sont mis en cache après leur premier chargement.

Le nom d’un module provient de son nom de fichier, donc camera_utils.py est importé via import camera_utils.

2.19.2. Modules multifichiers (packages)

Un module peut aussi être un répertoire de fichiers plutôt qu’un seul .py. Le nom du répertoire devient le nom du module, et les fichiers qu’il contient sont ses sous-modules :

camera_utils/
    __init__.py
    text.py
    timing.py

__init__.py est le fichier qui s’exécute lorsque le package lui-même est importé ; il peut être vide, ou réexporter des noms choisis depuis les sous-modules. On accède aux sous-modules avec un nom pointé :

import camera_utils.text
from camera_utils.timing import elapsed

camera_utils.text.label("ready")

À l’intérieur du package, les sous-modules peuvent s’atteindre mutuellement soit par le nom pointé complet, soit par un import relatif qui utilise un point initial pour signifier « ce package » :

camera_utils/timing.py

from . import text             # the sibling submodule

def stamp(value):
    return text.label(str(value))

Pour importer un nom spécifique à la place, nommez-le après le frère pointé :

from .text import label

def stamp(value):
    return label(str(value))

Les imports relatifs gardent un package autonome : renommer le répertoire du package n’oblige pas à modifier chaque sous-module.

Utilisez un package quand un fichier unique dépasse une taille confortable, ou quand un ensemble de modules apparentés a sa place ensemble sous un même espace de noms. Pour les scripts du quotidien, un seul fichier .py suffit.

2.19.3. La protection __name__

Chaque module possède un nom intégré __name__. Sa valeur dépend de la façon dont le fichier est utilisé :

  • Lorsque le fichier est exécuté directement, __name__ prend la valeur de la chaîne "__main__".

  • Lorsque le fichier est importé par un autre script, __name__ prend la valeur du nom du module – le nom de fichier sans .py.

L’idiome qui exploite cela est :

label_util.py

def label(text):
    return "[" + text + "]"

if __name__ == "__main__":
    print(label("self-test"))

Sortie (lors d’une exécution directe)

[self-test]

Lorsque le même fichier est importé à la place, __name__ prend la valeur du nom du module, donc le bloc if est ignoré et rien d’autre ne s’exécute :

>>> import label_util
>>> label_util.__name__
'label_util'

Utilisez ce schéma pour attacher un test de fumée rapide ou une démonstration à un fichier de bibliothèque sans perturber les scripts qui l’importent.