Modularité
La modularité d'un programme informatique traduit le fait que celui-ci découpe les actions qu'il doit faire en taches les plus élémentaires possibles; chacune de ces taches correspond généralement à une fonction.
Un programme très modulaire a un code qui est beaucoup plus clair à lire, et sa maintenance est beaucoup plus facile.
Un module est un fichier contenant du code ( dans notre cas, en Python, c’est-à-dire tout fichier avec l’extension .py), que l'on
pourra ensuite importer dans son propre script de façon à utiliser les fonctionnalités que ce code additionnel propose.
Les modules permettent alors la séparation et donc une meilleure organisation du code. En effet, il et courant dans un projet de découper son code en différents fichiers qui vont contenir des parties cohérentes du programme final pour faciliter la compréhension générale du code, la maintenance et le travail d’équipe si on travaille à plusieurs sur le projet.
En Python, on peut distinguer trois grandes catégories de module en les classant selon leur éditeur :
- Les modules de la distribution standard, qui sont disponibles directement et peuvent donc être automatiquement importés par Python.
Nous avons déjà utilisé de nombreux modules de ce type :random,math,turtleetc... - Les modules développés par des développeurs externes, qui doivent dans ce cas être préalablement installés sur la machine à partir d'un dépôt distant, comme PyPI,
à l'aide du programme
pip.
Le traceur de graphiqueMatplotlib, ou le module de création de jeuPyxelfont partie de cette catégorie. - Les modules que l'on développe soi-même.
Importer un module dans un script Python
Quelle que soit l'origine d'un module, la manière de l'importer dans son propre script est la même.
Mais il existe plusieurs façons d' importer une fonction ou un module dans un programme; essayons de comprendre les différentes possibilités :
La première méthode consiste à n'importer qu'une seule fonction d'un module, en la nommant :
from random import randint
print(randint(0,10))
Dans ce cas on ne peut utiliser que la fonction importée.
Mais il est bien entendu possible d'importer plusieurs fonctions du module, en séparant leurs noms par des virgules :
from random import randint, shuffle
l = [randint(1,100) for i in range(100)]
l.shuffle()
Si un module est largement utilisé, il devient alors compliqué d'importer de cette façon de nombreuses fonctions; on peut être tenté de taper :
from random import *
print(randint(0,10))
Très pratique mais un peu dangereux ... En effet, si vous utilisez deux modules de cette façon il peut y avoir une collision de nom :
si deux modules comportent deux fonctions ayant le même nom, cela va poser problème...
Donc si l'on veut absolument importer des modules entiers dont les fonctions seront largement utilisées, il est préférable de taper :
import random
print(random.randint(0,10))
Dans ce cas, il est alors nécessaire de préfixer chaque fonction du module par le nom du module. Cela évite les confusions en cas d'homonymie.
Comme le nom du module peut être un peu long à taper et risque de revenir souvent (et également pour plus de lisibilité...), il est possible d'utiliser un alias pour le nom du module :
import random as rdm
print(rdm.randint(0,10))
Dans ce cas le préfixage est plus simple.
Lorsque l'on importe un module il est important de savoir ce que l'on fait. Il faut :
- Dans la mesure du possible, limiter l'import aux fonctions que l'on va utiliser
- Éviter d'utiliser
from module import * - Utiliser des alias judicieux pour un code plus lisible
Aide sur un module
Lister les fonctions d'un module
Pour connaître la liste des fonctions proposées par un module, utiliser dans la console ( après avoir importé le module ! ) la commande : dir(module) :
>>> dir(random)
['BPF', 'LOG4', 'NV_MAGICCONST', 'RECIP_BPF', 'Random', 'SG_MAGICCONST', 'SystemRandom', 'TWOPI', '_ONE', '_Sequence', '_Set', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_accumulate', '_acos', '_bisect', '_ceil', '_cos', '_e', '_exp', '_floor', '_index', '_inst', '_isfinite', '_log', '_os', '_pi', '_random', '_repeat', '_sha512', '_sin', '_sqrt', '_test', '_test_generator', '_urandom', '_warn', 'betavariate', 'choice', 'choices', 'expovariate', 'gammavariate', 'gauss', 'getrandbits', 'getstate', 'lognormvariate', 'normalvariate', 'paretovariate', 'randbytes', 'randint', 'random', 'randrange', 'sample', 'seed', 'setstate', 'shuffle', 'triangular', 'uniform', 'vonmisesvariate', 'weibullvariate']
Importance des annotations et de la docstring dans un module
Lorsque vous cherchez des informations sur un module ou une commande Python vous allez souvent sur internet...
Mais de nombreuses informations sont données directement dans la console Python grâce à la commande help()
Documentation d'une classe/d'une fonction
Si vous tapez dans un shell Python :
>>> t=[]
>>> help(t)
Vous obtiendrez l'aide de base sur la classe list de Python, toutes les méthodes existantes, etc ....
Documentation d'un module
De la même façon on peut obtenir la documentation d'un module complet; dans un shell Python, tapez :
>>> help()
help> random
Vous obtenez ainsi l'aide complète du module ( tapez quit pour sortir de l'utilitaire d'aide )....
Cette aide est très dense et assez peu facile à parcourir, mais cela peut permettre de vérifier l'existence d'une fonction par exemple.
On peut également, après avoir importé le module, taper directement :
>>> help(random)
Documentation d'une fonction d'un module
Cette dernière fonctionnalité est la plus utilisée. Si vous avez un doute sur la syntaxe d'un fonction, plutôt que de traîner sur internet, tapez :
>>> help(random.randint)
...et vous obtenez immédiatement une aide succincte mais efficace :
Help on method randint in module random:
randint(a, b) method of random.Random instance
Return random integer in range [a, b], including both end points.
Pour obtenir de l'aide sur une classe, un module, une fonction, il faut dans un premier temps utiliser la commande help()/
Mais d'où vient cette aide ? Les développeurs de python doivent donc documenter tout ce qu'ils font ?
En fait l'affichage généré par la commande help() est l'affichage de la docstring de chaque fonction !!!
C'est pourquoi il est essentiel de documenter correctement la docstring lors de la construction d'un module. C'est la première aide pour l'utilisateur de la fonction.
Cette aide est de qualité car elle vient du développeur ou du groupe de développeur directement.
La commande help() affiche la docstring d'une fonction, d'où son importance !
help() répondez aux questions ci-dessous :
- Combien de décimales de pi sont retenues dans le module
math? - Que fait la fonction
remainder? - Que renvoie la fonction factorielle (
factorial) pour un nombre négatif ou un nombre non entier ? - De quel type sont les deux arguments de la fonction
gcd - Quelle fonction du module pourrait calculer facilement la hauteur d'un arbre binaire complet équilibré de taille n ?
Valider les préconditions : les assertions
Quand on construit un module il est important de le documenter clairement afin que d'autres développeurs puissent l'utiliser.Mais il est également important que ce module soit robuste.
Comme un développeur ne lit "pas toujours" (hum hum) l'intégralité de la docstring des fonctions qu'il utilise, il est nécessaire de protéger votre code des entrées incongrues (calcul de la factorielle d'une str, d'un nombre négatif...). Il existe pour cela les assertions. Lisez ce paragraphe qui vous expliquera en détail l'utilisation de la fonction
assert.
Tester un module sans perturber le programme principal
Comme pour tous les codes que l'on écrit en python, il est nécessaire de tester notre production. Cependant, quand on code un module qui aura vocation à être importé et utilisé par d'autres programmeurs dans d'autres codes, la situation est un peu délicate ...Il est possible de laisser des tests visibles dans un module, sans qu'ils ne s'exécutent quand on l'importe.
Pour cela il est nécessaire des placer ces tests sous l'instruction :
if __name__ == '__main__':
# tests...
- Si le script est importé par un autre à l'aide de l'instruction
import mon_module, le code ne sera pas exécuté. - Si le script est directement exécuté, le code sera lu et interprété.
'__main__'
si on l'affiche dans le programme qui est directement exécuté, et la chaîne de caractères 'mon_module' si elle est affichée dans un module importé.
Une application : modultab
Structure
Maintenant que vous en savez un peu plus sur la modularité, nous allons créer un module permettant de gérer quelques fonctions avancées sur les tableau en python.L'idée est la suivante : mettre au point un module
modultab qui permet de réaliser des fonctions avancées sur des tableaux d'entiers (recherche de minimum et de maximum, somme et produit de tableau, calcul de dénivelée etc... )Mais vous ne partez pas de rien, voici un programme principal qui utilise votre futur module
modultab :
Bien entendu, ce script ne fonctionne pour l'instant pas...
Consignes
modultab.py) qui est importé en début du code précédent. Il devra contenir :
- Les fonctions listées dans le programme principal.
- dans ces fonctions, on écrira les assertions nécessaires pour que le code fonctionne correctement ( par exemple, vérifier que la variable t passée en argument est bien un tableau de valeurs, qu'il n'est pas vide pour le calcul de la moyenne, etc...)
- Les docstrings claires et détaillées de ces fonctions (c'est ce qui apparait quand on appelle l'aide du module avec
help()). - Les tests de votre module sous la section
if __name__ == '__main__'(voir ci-dessus). - le code de votre module devra être le plus...modulaire possible !😉 Voire même, il pourra éventuellement importer d'autre(s) module(s) créé(s) par vous-même ou pas !...
sort, len, min, max etc ...
Une fois votre module écrit et testé, enregistrez-le de façon à pouvoir le charger dans l'éditeur précédent, et faire fonctionner le script correspondant.