- Langages et programmation -
Gestion des bugs

Avant de recenser les erreurs, commençons par apprendre à les « lire ».

I. Anatomie d’une exception Python

Les lignes ci-dessous sont adaptées de ce post de forum.

Lorsqu’un programme Python plante, c’est en général (si ce n’est toujours…) en raison d’une exception non gérée. Voici le genre d’affichage que cela produit dans la console :

Traceback (most recent call last):
File "test.py", line 6, in <module>
    test()
File "test.py", line 3, in test
    print table[4]
IndexError: list index out of range

Ces erreurs affichées par Python se lisent de bas en haut. La ligne la plus importante est donc la dernière !

IndexError: list index out of range

Nous y trouvons deux informations :

Les lignes qui précèdent la dernière sont ce que l’on appelle le traceback. C’est ce qui permet de trouver rapidement où s’est produite l’erreur. Ces lignes vont en général 2 par 2 :

File "test.py", line 3, in test
print table[4]

La première ligne indique :

La seconde ligne présente l’instruction qui a fait planter le programme.

Les paires de lignes qui précèdent celle-ci sont les appels de fonctions successifs qui ont mené l’interpréteur jusqu’à cette instruction. Cela permet de savoir grosso-modo où en était votre programme dans son exécution avant qu’il ne plante.

II. Les différentes erreurs

Cette section est bien entendue non exhaustive !

1. IndentationError :

Tout d’abord un code générant cette erreur :

if nombre >=0:
print(nombre)

L’erreur vient du fait que le sous-bloc (à l’intérieur du if) doit être indenté, décalé. L’usage veut que l’on utilise soit quatre espaces soit une tabulation.

Le même code corrigé :

if nombre >=0:
    print(nombre)

2. SyntaxError :

Code générant l’erreur :

form random import randint

Il s’agit le plus souvent d’une faute de frappe : ici on a tapé form au lieu de from.

Le même code corrigé :

from random import randint

3. NameError :

Code générant l’erreur :

a = 5
print(2*b)

La variable b n’a pas encore été définie ! Python le signale.

Le même code corrigé :

a=5
b = 2*a
print(b)

4. ImportError :

Code générant l’erreur :

import module_inexistant

Le module_inexistant n’est pas identifié par Python. L’erreur peut provenir d’une faute de frappe lors de la saisie de son nom (courant) ou d’un problème d’installation. Il faut dans ce cas vérifier le path de python.

5. IndexError :

a = [0,1]
print(a[2])

Ici on demande à Python d’afficher le 3ème élément de a (d’indice 2). Or a ne possède que 2 éléments : on obtient une erreur d’indice. Il faut vérifier nos appels :

a = [0,1]
print(a[1])

6. TypeError :

from math import sqrt
sqrt("mot")

La fonction sqrt prend en argument des nombres or ici on lui propose une chaîne de caractère… On peut facilement éviter ce genre d’erreur lisant la documentation de la fonction (avec help par exemple) :

In [1]: help(sqrt)
Help on built-in function sqrt in module math:

sqrt(x, /)
    Return the square root of x.

III. Erreurs de conception de programme

Dans certains cas, un programme n’effectue pas ce que l’on souhaite sans que Python ne détecte d’erreur. Il est donc plus compliqué de trouver la source du problème. Voici quelques cas à envisager.

1. Condidions non-exhaustives

Observons le code ci-dessous :

a = 5
if a < 5 :
    print("Plus petit")
elif a > 5 :
    print("Plus grand")

Les conditions sont mal formulées, l’exécution ne fournira aucun affichage ! Il faudrait plutôt avoir :

a = 5
if a < 5 :
    print("Plus petit")
elif a >= 5 :
    print("Plus grand ou égal")

2. Effets de bords

Il peut arriver qu’une instruction modifie des variables annexes sans que l’on s’en rende compte. C’est souvent le cas lorsque l’on utilise des listes :

In [1]: liste = [[0] * 3] * 2

In [2]: liste
Out[2]: [[0, 0, 0], [0, 0, 0]]

In [3]: liste[0][1] = "ici"

In [4]: liste
Out[4]: [[0, 'ici', 0], [0, 'ici', 0]]

Dans cet exemple, on crée une liste contenant deux fois la sous-liste [0, 0, 0]. Dans l’instruction de la ligne 3, on modifie le deuxième élément de la première sous-liste.

On constate dans la ligne 4 que les deux sous-listes ont été modifiée. En effet, pour Python, ces deux sous-listes sont un seul et même objet comme le montre la fonction id qui donne l’identifiant de l’object :

In [5]: id(liste[0])
Out[5]: 1482323031432

In [6]: id(liste[1])
Out[6]: 1482323031432

La méthode la plus efficace, mais radicale, pour éviter les effets de bords est d’utiliser la programmation fonctionnelle

3. Egalité de flottants

Python, à l’instar de tous les langages de programmation, code les nombres réels sous forme binaire. Or les seuls nombres pouvant s’écrire parfaitement (sans une partie décmale infinie) sous forme binaire sont ceux de la forme \(\frac{n}{2^k}\) (un entier divisé par une puissance de \(2\)).

On arrive facilement à des erreurs :

In [1]: 1-1/3-2/3
Out[1]: 1.1102230246251565e-16

Ici \(\frac{1}{3}\) est mal codé par Python, donc le calcul est faux.

Il ne faut donc jamais tester l’égalité de nombres flottants, les erreurs de calculs peuvent engendrer des erreurs :

In [2]: a = 1

In [3]: while a != 0 :
    ...:     a = a - 0.1
    ...:     print(a)
0.9
0.8
0.7000000000000001
0.6000000000000001
0.5000000000000001
0.40000000000000013
0.30000000000000016
0.20000000000000015
0.10000000000000014
1.3877787807814457e-16
-0.09999999999999987

Ici, \(0,1\) ne s’écrie par correctement en python donc on n’a pas \(1 - 10 \times 0,1 = 0\).

Une bonne façon de gérer ces cas de figures est :

In [4]: a = 1

In [5]: while a > 0 :
    ...:     a = a - 0.1
    ...:     print(a)
0.9
0.8
0.7000000000000001
0.6000000000000001
0.5000000000000001
0.40000000000000013
0.30000000000000016
0.20000000000000015
0.10000000000000014
1.3877787807814457e-16
-0.09999999999999987