Skip to content

II. Bonnes pratiques de programmation

Cours

A. Spécification des fonctions

A.1. Prototypage

On définit est_majeur(age) qui prend en paramètre un entier age et renvoie un booléen valant True si age est supérieur à 18 et False sinon.

Pour avoir les informations sur le type des entrées et des sorties, on ajoute l'information de la manière suivante :

Code

def est_majeur(age : int) -> bool:
    majeur = age >= 18
    return majeur

Cela correspond à la signature de la fonction, que l'on peut aussi écrire sous la forme int -> bool.

A.2. Préconditions et postconditions

Dire qu'age doit être un entier ne suffit pas à décrire les valeurs que la variable peut prendre. En effet, un age négatif n'a pas de sens... alors que le type int en Python peut stocker un entier négatif comme un entier positif. Il faut donc rajouter la condition que age doit être positif (ou nul).

Une condition que doit respecter une variable d'entrée est une précondition, et une condition que doit respecter une variable de sortie est une postcondition.

Indiquer le(s) précondition(s) et postcondition(s) de l'exemple sur le schéma ci-dessous :

graph LR
age --> F[est_majeur];
  F --> majeur

A.3. Documentation

La documentation d'une fonction, aussi appelée docstring (documentation écrite avec une chaîne de caractères), précise ce qu'elle doit faire. Elle rajoute des informations par rapport à son nom (qui doit déjà être bien choisi), et peut contenir le type des variables si la signature de la fonction n'est pas écrite.

Exemple

Code

def est_majeur(age):
    """ Renvoie True si l'entier age est supérieur à 18 et False sinon
    """
    majeur = age >= 18
    return majeur

B. Utilisation de modules

Une bibliothèque ou module est un ensemble de fonctions déjà écrites, et regroupées ensemble car elle servent pour même un domaine d'application.

Exemples

La bibliothèque random contient des fonctions permettant de simuler l'aléatoire, turtle des fonctions permettant de manipuler une tortue pour dessiner, matplotlib sert à visualiser des données.

Il y a différentes manières de faire un import des fonctions dans votre programme Python, en voici 2 à retenir :
- from bibliotheque import fonction1, fonction2 : importe seulement fonction1 et fonction2 de la bibliothèque,
- from bibliotheque import * : importe toutes les fonctions de la bibliothèque. Attention ! si vous importez plusieurs bibliothèques et qu'elles ont des fonctions du même nom, celles du premier import seront écrasées.

C. Structure d'un programme

Les différents éléments d'un programme doivent conserver la structure suivante :

Code

# --------- Import des modules utilisés ---------
from bibli1 import fonction1, fonction2

# --------- Définition des fonctions ---------
def ma_fonction1():
    ...

def ma_fonction2(param):
    ...

# --------- Programme principal --------- 
# Déclaration des variables
a = 1
b = "bonjour"
# Test des fonctions
assert ma_fonction1() == "quelque chose"
assert ma_fonction2(b) == 25

Il faut d'abord définir ce dont on a besoin (import des modules et définition des fonctions), avant de l'utiliser (programme principal).

D. Test de programmes

Tester un programme revient à :
- tester chacune de ses fonctions,
- tester le fonctionnement global du programme.

Pour tester une fonction, on définit plusieurs cas qui correspondent à différents comportements de la fonction. Par exemple, si on a une fonction avec plusieurs conditions comme la suivante :

Code

def nb_jours_debut_annee(mois):
    if mois == 2:
        nb_jours = 28
    elif mois%2 == 0:
        nb_jours = 30
    else:
        nb_jours = 31
    return nb_jours

On teste alors ce qu'il se passe pour les cas où :
- mois vaut 2,
- mois est pair, par exemple vaut 6,
- mois est impair, par exemple vaut 3.

Les tests peuvent se faire :
- dans la console,
- avec des assertions lorsque la fonction renvoie une valeur, dans le programme principal. Ils seront alors conservés dans le fichier .py.

En pratique, commencez par faire les tests dans la console pour vérifier que l'on obtient le résultat attendu, puis écrivez-les dans l'éditeur sous la forme d'assertion.

Exemple

Ecrire les appels de fonction à faire dans la console, puis les assertions correspondante à conserver dans le fichier .py.

###


TD : Débogage et spécifications

A. Déboguer un programme

Pour chacun des exemples donnés, indiquer :
- l'erreur affichée dans la console après exécution ou bien le comportement du programme,
- la signification de cette erreur,
- comment corriger ce code pour qu'il fasse ce qui était attendu.

  1. Affichage de "Bonjour, monde" :

    Code

    print("Bonjour, monde"
    
  2. Calul de la somme des nombres de 1 à 10 compris :

    Code

    for i in range(1, 11):
        total = total + i
    print("La somme est :", total)
    
  3. Affichage de chaque entier de 1 à 10 compris :

    Code

    i = 1
    while i <= 10:
        print(i)
    
  4. Affichage des carrés des entiers de 1 à 5 :

    Code

    for i in range(1, 6):
    print(i ** 2)
    
  5. Définition d'une fonction multiplier multipliant deux paramètres a et b :

    Code

    def multiplier(a, b):
        resultat = a * b
        return resultat
    x = 7
    y = 2
    print("Le résultat de la multiplication est :", multiplier())
    
  6. Définition d'une fonction ajouter_trois ajoutant 3 au paramètre x :

    Code

    def ajouter_trois(x):
        resultat = x + 3
    nombre = 5
    print("Le résultat est :", resultat)
    

B. Spécifications

  1. Réécrire la fonction mystere du TP "Fonctions et portée d'une variable" en :

    • changeant son nom, pour en avoir un plus explicite,
    • ajoutant sa signature,
    • ajoutant sa documentation.

    Code

    def mystere(a):
        p = 1
        for i in range(a):
            p = 2*p
        return p
    
  2. Un élève de NSI veut pouvoir calculer sa moyenne en fonction des notes qu'il a déjà, et de celle de la dernière évaluation. Il a déjà eu 11.5, 15 et 14, de coefficients 1, 2 et 4. La prochaine évaluation aura un coefficient 4. Il veut prévoir sa moyenne en fonction de sa dernière note, et écrit une fonction moyenne(note) prenant en paramètre cette dernière note.

    a. Donner le prototypage de sa fonction.

    b. Décrire les préconditions sur les paramètres et les postconditions sur le résultat.


    c. Ecrire la fonction moyenne(note) en ajoutant des assertions pour les préconditions et postconditions, et la signature de la fonction.

    
    

TP : Prototypage et tests

Créer un fichier TP_Prototypage.py dans votre dossier NSI, et dans le dossier seq4. Ecrire les réponses aux questions sur votre cahier de TP.

Vous êtes développeur Python et on vous demande d'écrire une fonction calculant la racine carrée d'un nombre. On demande que chaque fonction soit documentée de la manière suivante (il n'est pour l'instant pas demandé de remplir le tableau) :

Etape Question Réponse
1 Quel nom la fonction peut-elle avoir ?
2 Combien de paramètres sont acceptés en entrée ? De quel type ?
3 Quels sont les valeurs acceptées en entrée ?
4 Combien de valeurs sont renvoyées en sortie ? Préciser le type.
5 Quels sont les valeurs en sortie possibles ?

N.B.: La racine carrée d'un nombre est toujours positive ou nulle et n'est calculée que pour un nombre qui est aussi positif ou nul.

  1. Les informations demandées permettent de prototyper la fonction. En déduire ce que veut dire prototyper une fonction.

    Plus précisément :

    • L'étape 3 décrit les préconditions de la fonction : les conditions que doivent vérifier ses entrées,
    • L'étape 5 décrit les postconditions de la fonction : les conditions que doivent vérifier les sorties.
  2. Compléter le tableau.

  3. On donne le code suivant, implémentant la méthode de Héron pour trouver la racine carrée d'un nombre :

    Code

    def racine(x):
        y = 1
        for i in range(10):
            y = (y+x/y)/2
        return y
    

    Réécrire ce code dans l'éditeur et le compléter avec sa spécification sous la forme suivante (remplacer type_de_a par le type de a, type_de_x par le type de x, et la partie "Rôle de la fonction" par ce qu'elle fait) :

    Code

    def racine(x : type_de_x) -> type_de_y:
        ''' Rôle de la fonction
        '''
        y = 1
        for i in range(10):
            y = (y+x/y)/2
        return y
    
  4. Après avoir exécuté votre code, consulter sa documentation en tapant dans la console la commande :

    help(racine)
    

  5. Faites l'appel de racine(-1) dans la console. Que se passe-t-il ? Est-ce que le comportement de la fonction est clair pour l'utilisateur ?

  6. On ne peut pas toujours faire confiance à l'utilisateur pour bien utiliser un code. Nous allons le forcer à respecter le type de l'entrée, avec une assertion. La syntaxe est la suivante :

    assert expression booléenne testant ce qui nous intéresse, "Message que l'on veut voir apparaître lorsque le test renvoie False"
    
    Compléter votre fonction en ajoutant l'assertion avant les premières instructions de la fonction :

    Code

    def racine(x: ...)-> ...:
        """ Garder la documentation de la question précédente """
        assert x >= 0 , "à compléter avec un message indiquant à l'utilisateur quel est le problème"
        ...
    

    Exécuter le code. Que se passe-t-il maintenant lorsque vous appelez la fonction (dans la console) avec l'argument -1 ?

  7. Les assertions peuvent aussi être utilisées pour vérifier que l'on obtient le bon résultat en sortie d'une fonction. On teste donc si un appel de fonction donne le résultat attendu. Ajouter, dans le programme principal (donc en dehors de la définition de racine) :

    Code

    assert round(racine(4)) == 2, "Erreur pour la valeur 4"
    

    a. Exécuter le programme, et vérifier que rien ne se passe (cela veut dire que le test fait dans l'assertion renvoie True).
    b. La fonction round permet d'arrondir le résultat, pour qu'il soit un entier. Pourquoi fait-on cette opérations ici (faire le lien avec le cours sur la représentation des nombres) ?
    c. Rajouter 2 autres assertions, avec des tests différents, pour vérifier le bon fonctionnement de votre fonction.


TP : Bibliothèque random et stockage dans des fichiers

Code
Questions

Créer un fichier TP_Bibliotheque_Fichiers.py dans votre dossier NSI, et dans le dossier seq4. Ecrire les réponses aux questions sur votre cahier de TP.

A. Trouver le nombre

L'objectif de cette partie est de programmer un jeu dans lequel on demande à l'utilisateur de deviner un nombre, qui est choisi aléatoirement par l'ordinateur. Pour avoir ce nombre aléatoire, on utilise la bibliothèque random qui sert à simuler l'aléatoire, et particulièrement la fonction randint.

On veut que le programme :

  • stocke dans une variable nombre_mystere le nombre à deviner compris entre 0 et 100,
  • demande à l'utilisateur d'entrer un nouveau nombre (avec la fonction input) tant qu'il n'a pas trouvé la solution ou qu'il a atteint le maximum de 10 essais,
  • si l'utilisateur entre un nombre trop grand, l'ordinateur affiche "Le nombre mystère est inférieur", s'il est trop petit, l'ordinateur affiche "Le nombre est supérieur",
  • si l'utilisateur a deviné, le programme s'arrête et il affiche le nombre d'essais de l'utilisateur.
  1. Reformuler la description de l'algorithme utilisé en faisant apparaître la structure du programme, et les instructions à exécuter par l'ordinateur ligne après ligne.

  2. Importer la fonction avec l'instruction suivante à placer en début de programme from random import randint.

  3. Avec l'instruction help(randint) (tapée dans la console), on obtient sa documentation : son prototypage, et ce qu'elle fait.

    randint(a, b) method of random.Random instance
        Return random integer in range [a, b], including both end points.
    
    a. Quels sont ses paramètres ?
    b. Que renvoie-t-elle ?

  4. Déduire des questions précédentes comment utiliser randint pour que l'ordinateur choisisse le nombre mystère.

  5. Implémenter (traduire en programme) l'algorithme de la question 1. > N'oubliez pas que la valeur renvoyée par input est une chaîne de caractères, que vous devez ici convertir en entier avec la fonction int.

  6. Tester le programme en :

    • entrant 10 fois un mauvais nombre pour vérifier que le programme s'arrête,
    • essayant de bien deviner le nombre et en vérifiant que le nombre de coups affiché correspond au nombre de fois que vous avez proposé un nombre.

B. Stockage du score dans un fichier

On veut maintenant sauvegarder le score de l'utilisateur dans un fichier. Pour ouvrir un fichier, on utilise la fonction open, et pour écrire la fonction write.

  1. open prend en paramètres un nom de fichier et un mode d'ouverture (et on peut ajouter d'autres paramètres). Avec l'instruction help(open), trouver à quoi correspondent les différents modes possibles.

  2. Pour écrire dans un fichier, il faut l'ouvrir puis écrire. Cela s'écrit avec une syntaxe proche de celle du code suivant :

    Code

    mot = "ce que je veux stocker"
    with open('fichier.txt','w') as f :
        f.write(mot)
    
    • Ce code enregistre dans le fichier fichier.txt le contenu de la variable mot.
    • Le fichier est accessible avec la variable nommée ici f.
    • Pour écrire avec la fonction write, on utilise une syntaxe un peu particulière : f.write(...)

    Modifier ce code et le placer à la fin de votre programme de manière à stocker le score qui est le nombre de coups qu'a mis l'utilisateur pour trouver le nombre mystère. Le fichier utilisé se nommera score.txt. Ce qui est écrit dans le fichier doit être de type chaîne de caractères, vous pouvez faire la conversion avec la fonction str().

Pour aller plus loin... (à faire si vous avez le temps)
Faire en sorte que lorsque l'utilisateur rejoue une partie, le score stocké dans le fichier ne soit actualisé que si le nouveau score est meilleur.