Connexion élèves

Choisir le(s) module(s) à installer :

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, turtle etc...
  • 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 graphique Matplotlib, ou le module de création de jeu Pyxel font partie de cette catégorie.
  • Les modules que l'on développe soi-même.
legos

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 !

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 quel type 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 ?

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.

Une application : création de QCM

Structure

Maintenant que vous en savez un peu plus sur la modularité, nous allons créer un module permettant la création et la correction de QCM en python.
L'idée est la suivante : le QCM est stocké dans un tableau de tableaux comme présenté ci-dessous :
qcm

QCMNSI=[["en binaire 7 s'écrit...",'Qui a inventé les bases de données relationnelles','le transistor a été inventé en ...'],
       ['11','*Codd','1851'],
       ['1111','Rodd','*1948'],
       ['*111','Modd','1981']]
      
Cette structure de données étant posée, vous allez devoir créer les fonctions qui permettent de : Ces fonctions permettront de faciliter le déroulement du QCM qui sera de cette forme (pour un QCM à une seule question...):

en binaire 7 s'écrit...
1. 11
2. 1111
3. 111

entrez votre n° de réponse : 1

|---------------------------------------------------------|
|             votre score est de  0  sur  1               |
|---------------------------------------------------------|

|---------------------------------------------------------|
|                        CORRECTION                       |
|---------------------------------------------------------|
en binaire 7 s'écrit...
3. 111	
		
Heureusement le chef de projet a déjà structuré le travail et décrit les contours des fonctions

def creer_tableaurep(QCM:list)->list:
    '''
	permet de créer un tableau ou seront stockées les réponses données par l'utilisateur (1, 2 ou 3 pour chaque question)
	le tableau renvoyé a pour longueur le nombre de questions du QCM soit len(QCM[0]) 	
    '''

def affichageQCM(QCM:list,i:int):
    '''
    permet d'afficher la question n°i du QCM contenue dans le tableau QCM.
    les réponses sont ensuite proposées
    l'étoile indiquant la bonne réponse est supprimée lors de l'affichage
    '''

def score(QCM:list,rep:list)->list:
    '''
    calcule le score à chaque question (0 ou 1) en vérifiant si la réponse est bonne
    dans les tableaux de réponse grâce au caractère *. Le tableau renvoyé ne contient donc que des 0 ou des 1
    '''

def total(tab_score:list)->list:
    '''
    renvoie un tuple contenant le score total et le nombre de questions du type (7,10) quand le score est de 7/10
    '''

def affiche_resultat(tab_total:list):
    '''
    affiche le résultat de l’utilisateur à partir d'un tableau à deux cases contenant le score de l'utilisateur et le nombre de questions
    '''

def affichage_bonne_rep(QCM:list,i:int):
    '''
    permet d'afficher la question n°i du QCM contenue dans le tableau QCM puis uniquement la réponse correcte
    '''

def passageQCM(QCM,tabrep):
	'''
	fonction de passage de QCM : pour chaque question, affiche la question puis les trois propositions et enfin stocke le numéro de la réponse du candidat dans un tableau qui sera ensuite renvoyé.
	'''

def correctionQCM(QCM):
	'''
	Affiche la correction de l'ensemble du QCM (uniquement les questions et les bonnes réponses)
	'''
		

A réaliser

Comme vous avez pu le constater, le chef de projet était pressé et n'a pas rédigé des dosctring de bonne qualité. De plus il a oublié les annotations dans l'entête de certaines fonctions...mais bon l'essentiel est là et il va falloir faire avec. Vous devez donc, pour chaque fonction, et dans cet ordre ! :
  1. Compléter et éventuellement corriger la docstring (en pensant bien que cela sera l'aide aux utilisateurs du module)
  2. Compléter les annotations si ce n'est pas encore fait
  3. Prévoir des tests unitaires si cela est possible
  4. Coder la fonction en prévoyant de vérifier les entrées avec des assert quand cela est faisable

L'ensemble de votre travail sera enregistré dans un fichier QCMmodul.py
.
Pour finir, le chef de projet qui a tout de même une idée claire de ce qu'il veut vous propose de tester votre module avec ce programme principal que vous placerez dans un fichier passageQCM.py:
 
import QCMmodul as Q

QCMNSI=[["en binaire 7 s'écrit...",'Qui a inventé les bases de données relationnelles','le transistor a été inventé en ...'],
            ['11','*Codd','1851'],
            ['1111','Rodd','*1948'],
            ['*111','Modd','1981']]

reponses=Q.creer_tableaurep(QCMNSI)
reponses=Q.passageQCM(QCMNSI,reponses)
corrQCM=Q.score(QCMNSI,reponses)
resultat=Q.total(corrQCM)
Q.affiche_resultat(resultat)
Q.correctionQCM(QCMNSI)
				

SOLUTION

Bonus

Ce QCM n'est pas très facile à utiliser et il a de nombreux défauts...
en effet : Je vous propose donc de réaliser trois évolutions de votre module :
  1. modifiez votre module QCM afin qu'il construise le tableau de QCM à partir d'un fichier fichierqcm.txtdans lequel le concepteur aura saisi son QCM sous la forme suivante :
    
    en binaire 7 s'écrit...
    11
    1111
    *111
    Qui a inventé les bases de données relationnelles
    *Codd
    Rodd
    Modd
    le transistor a été inventé en ...
    1851
    *1948
    1981			 		
    			
  2. puis...modifiez votre module QCM afin de le lancer en ligne de commande depuis une fenêtre de shell avec une commande du type :
    
    cegpuk@cegpuk-bureau:~/Bureau/python$ python passageQCM.py fichierqcm.txt
    			
    Pour cela vous vous documenterez sur le module sys grâce à ce lien :
    https://python.sdv.univ-paris-diderot.fr/08_modules/#85-module-sys-passage-darguments
  3. Pour finir un peu de création et d'imagination. Tentez d'imaginer un système qui empêche à l'utilisateur d'aller voir les réponses directement dans le fichier fichierqcm.txt (en demandant, par exemple, au concepteur de lancer une commande qui "crypte" le fichier fichierqcm.txt...)
Bon courage !