Aller au contenu

Bien écrire un programme

A- Documentation d’un programme : DocString.⚓︎

La DocString est une chaîne de caractères que l’on n’assigne pas.
Elle est placée juste en dessous de la signature de la fonction.

Exemple d’une docstring sur une seule ligne :

🐍 Script Python
1
2
3
def ajouter(a, b):
    """ Ajoute deux nombres l'un à l'autre et retourne le résultat."""
    return a + b

Écrire des DocStrings offrent de nombreux avantages :

  • La fonction help() affiche cette documentation dans un interpréteur de commande (ici IDLE python).
    🐍 Script Python
    1
    help(ajouter)
    

    Help on function ajouter in module __main__:
    ajouter(a, b)
    Ajoute deux nombres l'un à l'autre et retourne le résultat.

Popup Docstring

  • Les outils de développement affichent cette documentation quand le programmeur écrit le nom de la fonction (fenêtre popup par exemple).
  • On peut générer une bonne documentation du code avec des commandes qui extraient ces DocStrings.
  • C’est un mécanisme standardisé de documentation : tout le monde sait que si c’est là, et que ça a cette forme, c’est une documentation.
  • On peut mettre des tests dans les DocStrings, qui servent alors d’exemples d’utilisation.

Il existe plusieurs manières de formater une DocString, et il y a même un PEP 257 (Python Enhancement Proposals) qui ne parle que de ça.

Normaly a docstring must be wrote in English !... Any programmer speak English. This avoid the accented characters.

Des balises standards sont mises à disposition pour formater la documentation.

Exemple de DocString sur plusieurs lignes :

🐍 Script Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    from math import sqrt      # On importe le module sqrt de la bibliothèque math

    def racine(x : float) -> float:
        """
            Renvoie la racine carrée d'un nombre entier ou décimal positif

            Paramètre :
            ----------
            x : float, nombre entier ou décimal positif ou nul dont on veut calculer la racine carrée

            Sortie :
            -------
            : float, valeur approchée décimale de la racine carrée de x

            Exemples :
            ---------
            >>> racine(4.0)
            2.0
            >>> racine(25)
            5.0
        """

        if not isinstance(x, (int, float)):       # pré-condition
            # On lève une exception
            raise TypeError('La variable x doit être un nombre')
        if x < 0:                                 # pré-condition
            # On lève une exception
            raise ValueError('Le nombre x doit être positif')
        else:
            return sqrt(x)

    # Début du programme principal
    if __name__ == '__main__':
        import doctest            # importation du module DocTest
        doctest.testmod()         # test des exemples présent dans le DocString
        x = float(input('Saisir une valeur positive pour x : '))

Remarques :

  • On remarque la DocString sur plusieurs lignes juste en dessous de la signature de la fonction. Elle contient notamment 2 exemples qui permettent de comprendre ce que renvoie la fonction et de tester son fonctionnement à l'aide du module DocTest.
  • doctest.testmod() exécute les exemples de la partie Exemples : du DocString et vérifie le résultat.
  • if not isinstance(x, (int, float)): et if x < 0: sont des pré-conditions qui permettent de s'assurer que les arguments passés à la fonction respectent bien le domaine de définition (x doit être un nombre positif).
  • from math import sqrt importe la fonction sqrt() du module math. C'est nécessaire pour pouvoir l'utiliser
  • Les instructions qui dépendent de la condition if __name__ == '__main__': ne sont exécutées que si le programme est exécuté en tant que programme principal. Ce qui ne sera pas le cas si on importe le fichier en tant que module depuis un autre fichier.

B- Tests unitaires. Utilisation des assertions⚓︎

Pour valider un projet informatique, on doit effectuer un bon nombre de tests. Une pratique de plus en plus exigée consiste à faire des tests automatiques pour chaque fonction.
On les appelle les tests unitaires.

Pour une organisation plus claire, on place l'écriture des fonctions dans un fichier (par exemple : math_functions.py) et les fonctions de tests dans un autre (test_math_functions.py). Par convention, le fichier de test commence par test_ suivi du nom du fichier contenant les fonctions à tester.

Exemple d'un fichier math_functions.py

🐍 Script Python
1
2
3
4
5
6
7
8
def cube(value):  
    if isinstance(value, (int, float)):  
        cubed_value = value * value * value  
    elif isinstance(value, list):  
        cubed_value = [i * i * i for i in value]  
    else:  
        raise Exception("Data Type to operate has not been implemented")  
    return cubed_value  

Pour tester la fonction, on écrire le script dans le fichier test_math_functions.py :

Fichier test_math_functions.py

🐍 Script Python
1
2
3
4
5
6
7
from math_functions import *  

def test_cube():  
    assert cube(-5) == -125, "résultat faux : cube(-5) == -125"   
    assert cube([1, 2, 3]) == [1, 8, 27], "résultat faux : cube([1, 2, 3]) == [1, 8, 27]"  

test_cube()  

Si les assertions sont validées, l’exécution du script ne renvoie rien.
Si on se trompe dans l'écriture de la fonction en écrivant par exemple cube_value = value * value, alors on obtient :

Assertion Error

Assertion Error

C- Exécution d’instructions localement au programme principal.⚓︎

Lorsqu'on écrit une fonction dans le fichier math_functions.py, on teste son fonctionnement à l'aide d'instructions placées à la fin du fichier (généralement des print()).
Pour autant, on ne veut pas que ces print() soient exécutées lors de l’import du fichier dans test_math_functions.py.
Pour cela on va conditionner leur exécution au fait que le fichier math_functions.py soit exécuté en tant que programme principal.
Pour cela, on utilise la condition : if __name__ == "__main__" :

Exemple pour le fichier math_function.py

🐍 Script Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def cube(x):
    if isinstance(x, (int, float)):     # pré-condition
        value = x * x * x
    elif isinstance(x, list):           # pré-condition
        value = [i * i * i for i in x] 
    else:
        raise Exception("Data Type to operate has not been implemented")
    return value

if __name__ == "__main__" :
    print(cube(-5))
    print(cube([1,3,5]))