Favicon

NSI Vaugelas - Terminale

Connexion

Sommaire

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 présente un code qui est beaucoup plus clair à lire, sa maintenance est beaucoup plus facile, et permet un travail en équipe beaucoup plus efficace (répartition des taches plus facile).

Deux aspects de cette notion seront (re-)vus dans ce chapitre :

  • le découpage d'un code en fonctions réalisant des taches les plus élémentaires possibles,
  • le découpage d'un code en plusieurs fichiers appelés modules, regroupant chacune des fonctionnalités autour d'un même "thème"
legos

Ecrire un code modulaire

Commençons directement par un exemple de ce qu'il ne faut pas faire lorque l'on commence à être un programmeur aguerri :


# Initialisation des listes
eleves = []
notes = []

# Ajout manuel de quelques élèves pour démarrer
eleves.append("Alice")
notes.append([12, 15, 10])

eleves.append("Bob")
notes.append([14, 13, 16])

eleves.append("Charlie")
notes.append([11, 12, 9])

# Affichage du menu principal
print("=== Gestion des élèves et des notes ===")
print("1. Ajouter un élève")
print("2. Ajouter une note à un élève")
print("3. Calculer la moyenne d'un élève")
print("4. Afficher la moyenne de la classe")
print("5. Afficher l'élève avec la meilleure moyenne")
print("6. Quitter")

# Boucle principale du programme
while True:
    # Saisie du choix de l'utilisateur
    choix = input("Entrez votre choix (1-6) : ")

    # Option 1 : Ajouter un élève
    if choix == "1":
        nom = input("Entrez le nom de l'élève à ajouter : ")
        eleves.append(nom)
        notes.append([])
        print(f"Élève {nom} ajouté avec succès !")

    # Option 2 : Ajouter une note à un élève
    elif choix == "2":
        nom = input("Entrez le nom de l'élève : ")
        if nom in eleves:
            index = eleves.index(nom)
            note = float(input(f"Entrez la note à ajouter pour {nom} : "))
            notes[index].append(note)
            print(f"Note {note} ajoutée pour {nom}.")
        else:
            print(f"Élève {nom} non trouvé.")

    # Option 3 : Calculer la moyenne d'un élève
    elif choix == "3":
        nom = input("Entrez le nom de l'élève : ")
        if nom in eleves:
            index = eleves.index(nom)
            liste_notes = notes[index]
            if len(liste_notes) > 0:
                somme = 0
                for note in liste_notes:
                    somme += note
                moyenne = somme / len(liste_notes)
                print(f"La moyenne de {nom} est : {moyenne:.2f}")
            else:
                print(f"{nom} n'a pas encore de notes.")
        else:
            print(f"Élève {nom} non trouvé.")

    # Option 4 : Afficher la moyenne de la classe
    elif choix == "4":
        somme_totale = 0
        nombre_notes = 0
        for liste in notes:
            for note in liste:
                somme_totale += note
                nombre_notes += 1
        if nombre_notes > 0:
            moyenne_classe = somme_totale / nombre_notes
            print(f"La moyenne de la classe est : {moyenne_classe:.2f}")
        else:
            print("Aucune note disponible pour calculer la moyenne de la classe.")

    # Option 5 : Afficher l'élève avec la meilleure moyenne
    elif choix == "5":
        meilleure_moyenne = -1
        meilleur_eleves = []
        for i in range(len(eleves)):
            liste_notes = notes[i]
            if len(liste_notes) > 0:
                somme = 0
                for note in liste_notes:
                    somme += note
                moyenne = somme / len(liste_notes)
                if moyenne > meilleure_moyenne:
                    meilleure_moyenne = moyenne
                    meilleur_eleves = [eleves[i]]
                elif moyenne == meilleure_moyenne:
                    meilleur_eleves.append(eleves[i])
        if meilleure_moyenne != -1:
            print(f"L'élève(s) avec la meilleure moyenne ({meilleure_moyenne:.2f}) : {', '.join(meilleur_eleves)}")
        else:
            print("Aucun élève n'a de notes.")

    # Option 6 : Quitter
    elif choix == "6":
        print("Au revoir !")
        break

    # Choix invalide
    else:
        print("Choix invalide. Veuillez entrer un nombre entre 1 et 6.")

    # Affichage du menu après chaque action
    print("\n=== Gestion des élèves et des notes ===")
    print("1. Ajouter un élève")
    print("2. Ajouter une note à un élève")
    print("3. Calculer la moyenne d'un élève")
    print("4. Afficher la moyenne de la classe")
    print("5. Afficher l'élève avec la meilleure moyenne")
    print("6. Quitter")
		

Il s'agit d'un petit programme de gestion des notes dans une classe.

On est bien d'accord, ce code est très difficile à lire, même par celui qui l'aurait écrit et qui le redécouvre 6 mois plus tard.
Imaginez vous alors dans la peau de celui qui devrait reprendre la suite de ce développeur, afin de corriger, de modifier ou d'améliorer ce code...

Analyse des problèmes

  1. Pas de fonctions : Tout est écrit dans une seule boucle, sans modularité.
  2. Répétitions : Le calcul de la moyenne est répété plusieurs fois.
  3. Variables globales : Les listes eleves et notes sont globales et modifiées directement.
  4. Code dupliqué : Le menu est affiché plusieurs fois de manière redondante.

Votre travail va donc être de tout réécrire correctement.

Modularisons...

Découpage en fonctions

Sur papier, réfléchir aux différentes fonctions qu'il va falloir écrire, et donc, ce qui "restera" dans le programme principal.

Ayez toujours à l'esprit l'idée qu'il y a derrière la notion de fonction : écrire un code réutilisable dans différentes situations (passage d'arguments différents), et qui réalise une seule tache.

Écrire le résultat de votre réflexion dans l'éditeur ci-dessous :

1. Fonctions à écrire :


2. Programme principal :


Correction

Écriture du code

Après cette phase de réflexion indispensable, on peut passer au codage proprement dit :

Transformer le code précédent en un programme le plus modulaire possible, implémentant les différentes fonctions identifiées précédemment.

Commencer par les fonctions réalisant les taches les plus simples.

Ne pas oublier le troisième point évoqué dans l'analyse des problèmes ( éviter l'utilisation de variables globales ).

Correction

Les modules en Python

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 est 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 origine :

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 :

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 !

Pour vous entraîner à utiliser la fonction help() répondez aux questions ci-dessous :
  1. Combien de décimales de pi sont retenues dans le module math ?
  2. Que fait la fonction remainder ?
  3. Que renvoie la fonction factorielle (factorial) pour un nombre négatif ou un nombre non entier ?
  4. De quels types sont les deux arguments de la fonction gcd ?
  5. Quelle fonction du module pourrait calculer facilement la hauteur d'un arbre binaire complet équilibré de taille n ?
# Vous pouvez utiliser directement la console de cet éditeur =>

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 de placer ces tests en fin de module, sous l'instruction :


if __name__ == '__main__':
    # code des tests...
			      

La variable __name__ est une variable réservée dans Python et gérée par le langage lui même. Elle contient la chaîne de caractères '__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 :

import modultab as mt from random import randint t = [randint(1, 100) for i in range(randint(10, 50))] # crée un tableau de 10 à 50 entiers aléatoires print(t) help(mt) # affiche l'aide du module print(mt.minimum(t)) print(mt.maximum(t)) print(mt.somme(t)) # renvoie la somme des valeurs du tableau print(mt.produit(t)) # idem pour le produit print(mt.longueur(t)) # nombre d'éléments print(mt.moyenne(t)) # moyenne des valeurs print(mt.inverse(t)) # renvoie un nouveau tableau avec les éléments en ordre inverse mt.tri(t) # tri en place ( algorithme au choix...) du tableau par ordre croissant des valeurs print(t) mt.tri(t, reverse = True) # idem par ordre décroissant print(t)

Bien entendu, ce script ne fonctionne pour l'instant pas...

Consignes

Vous devez donc créer le module python (modultab.py) qui est importé en début du code précédent. Il devra contenir :
Il va de soit que vous ne pouvez pas utiliser les fonctions déjà existantes dans python comme : 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.

# corps du module pass if __name__ == '__main__': # tests du module pass

SOLUTION