đ”ïžââïž Trace dâexĂ©cution
Lorsqu'on a un morceau de code, on peut souhaiter savoir dans quel ordre les instructions sont exécutées.
On veut spécifier précisément :
- Quelles lignes de code s'exécutent dans quel ordre.
- Les effets de chaque ligne de code :
- quelles variables sont créées/modifiées,
- quelles valeurs elles contiennent,
- ce qui est affiché (si on a des
print
).
- La portée des variables.
- Si on a des fonctions :
- la pile d'appels (quelles fonctions sont en cours d'exécution),
- quelle fonction est appelée,
- quelle valeur elle retourne (si elle en retourne une).
- Exemple simple
- Avec fonction
- Avec condition
- Avec raise/try/except
- Avec boucle
- Valider une trace
Dans le cas d'une séquence d'instructions, on peut voir que les instructions sont exécutées dans l'ordre de lecture du code. Simple!
Codeâ
Si on a le code suivant :
def carre(n):
return n * n
def affiche_resultat():
a = 2
b = 3
total = carre(a) + carre(b)
print(f'La somme des carrés est : {total}')
z = 42
affiche_resultat()
Traceâ
On aura la trace suivante :
portée globale | portée de affiche_resultat | portée de carre | |||||||
---|---|---|---|---|---|---|---|---|---|
#ligne | z | a | b | total | n | affichage | pile d'appels | fonction appelée | valeur retournée |
10 | 42 | __main__ | |||||||
11 | 42 | __main__ | affiche_resultat() | ||||||
5 | 42 | 2 | affiche_resultat __main__ | ||||||
6 | 42 | 2 | 3 | affiche_resultat __main__ | |||||
7 | 42 | 2 | 3 | affiche_resultat __main__ | carre(2) | ||||
2 | 42 | 2 | carre affiche_resultat __main__ | 4 | |||||
7 | 42 | 2 | 3 | affiche_resultat __main__ | carre(3) | ||||
2 | 42 | 3 | carre affiche_resultat __main__ | 9 | |||||
7 | 42 | 2 | 3 | 13 | affiche_resultat __main__ | ||||
8 | 42 | 2 | 3 | 13 | La somme des carrés est : 13 | affiche_resultat __main__ |
Explicationsâ
On indique la portée des variables en les regroupant par portée (premiÚre ligne).
Ici, on a la portée globale (variables définies en dehors de toute fonction), la portée de affiche_resultat
et la portée de carre
.
On remarque que la variable n
n'existe que dans la portée de la fonction carre
, et que les variables a
, b
et total
n'existent que dans la portée de la fonction affiche_resultat
.
La variable z
est dans la portée globale, c'est pourquoi elle est visible dans toute la trace.
On indique aussi la fonction appelée (avant son exécution) et la valeur qu'elle retourne (aprÚs son exécution) (derniÚres colonnes).
On indique aussi la pile d'appels, c'est-à -dire les fonctions en cours d'exécution (3e colonne à partir de la droite).
Ce genre de code fait souvent appel à plusieurs appels de fonctions imbriqués.
On peut donc visualiser la pile d'appels comme un empilement temporaire oĂč chaque appel sâajoute en haut de la pile, et disparaĂźt dĂšs quâil est terminĂ©.
Voici maintenant un petit exemple qui dĂ©montre l'importantce de spĂ©cifier la portĂ©e des variables, oĂč on a une variable x
dans la portée globale et une autre variable nommée x
dans la portée de la fonction ma_fonction
.
Codeâ
def ma_fonction(y):
x = 3 + y
print(f'dans ma_fonction, x vaut {x}')
x = 5
print(f'dans main, x vaut {x}')
ma_fonction(100)
print(f'dans main, x vaut toujours {x}')
Traceâ
portée globale | portée de ma_fonction | ||||||
---|---|---|---|---|---|---|---|
#ligne | x | y | x | affichage | pile d'appels | fonction appelée | valeur retournée |
5 | 5 | __main__ | |||||
6 | 5 | dans main, x vaut 5 | __main__ | ||||
7 | 5 | __main__ | ma_fonction(100) | ||||
2 | 5 | 100 | 103 | ma_fonction __main__ | |||
3 | 5 | 100 | 103 | dans ma_fonction, x vaut 103 | ma_fonction __main__ | ||
8 | 5 | dans main, x vaut toujours 5 | __main__ |
On voit que la variable x
dans la portée de ma_fonction
n'a rien Ă voir avec la variable x
dans la portée globale.
Cette derniÚre n'est pas modifiée par l'exécution de ma_fonction
, malgré le fait qu'on ait une instruction x = 3 + y
dans ma_fonction
et que la variable globale x
soit aussi accessible dans ma_fonction
(car elle est globale).
La raison est que l'instruction x = 3 + y
crée une nouvelle variable x
dans la portée de ma_fonction
, qui masque la variable globale x
.
Si on voulait modifier la variable globale x
dans ma_fonction
, il faudrait utiliser le mot-clé global
(ce qui est déconseillé en général).
Codeâ
Si on a le code suivant :
a = 42
if a > 100:
print('a est grand')
elif a > 10:
print('a est moyen')
else:
print('a est petit')
print('a est nul') if a == 0 else print('fin')
match a:
case 6:
print('a est égal à 6')
case 7:
print('a est égal à 7')
case 42:
print('a est égal à 42')
case _:
print("a n'est pas 6, 7 ou 42")
Traceâ
On aura la trace suivante :
portée globale | |||||
---|---|---|---|---|---|
#ligne | a | affichage | pile d'appels | fonction appelée | valeur retournée |
1 | 42 | __main__ | |||
5 | 42 | a est moyen | __main__ | ||
9 | 42 | fin | __main__ | ||
17 | 42 | a est égal à 42 | __main__ |
On voit que :
- On indique seulement les lignes qui s'exécutent en vrai.
- Certaines lignes ne s'exécutent pas, c'est le principe de la condition!
- On appelle ce cheminement le flot de contrĂŽle.
Codeâ
Si on a le code suivant :
def divise(a, b):
if b == 0:
raise ValueError("Division par zéro")
return a / b
try:
print(divise(10, 2))
print(divise(10, 0))
print(divise(10, 5))
except ValueError as e:
print(f'Erreur : {e}')
Traceâ
On aura la trace suivante :
portée globale | portée de divise | ||||||
---|---|---|---|---|---|---|---|
#ligne | a | b | affichage | pile d'appels | fonction appelée | valeur retournée | |
7 | __main__ | divise(10, 2) | |||||
4 | 10 | 2 | divise __main__ | 5.0 | |||
7 | 5.0 | __main__ | |||||
8 | __main__ | divise(10, 0) | |||||
3 | 10 | 0 | divise __main__ | ValueError | |||
11 | Erreur : Division par zéro | __main__ |
On voit que :
- On indique seulement les lignes qui s'exécutent en vrai.
- Certaines lignes ne s'exécutent pas, c'est le principe du try/except!
- On appelle ce cheminement le flot de contrĂŽle.
Codeâ
Si on a le code suivant :
i = 0
while i < 2:
print(f'i vaut {i}')
i = i + 1
for j in range(2):
print(f'j vaut {j}')
Traceâ
On aura la trace suivante :
portée globale | ||||||
---|---|---|---|---|---|---|
#ligne | i | j | affichage | pile d'appels | fonction appelée | valeur retournée |
1 | 0 | __main__ | ||||
3 | 0 | i vaut 0 | __main__ | |||
4 | 1 | __main__ | ||||
3 | 1 | i vaut 1 | __main__ | |||
4 | 2 | __main__ | ||||
7 | 2 | 0 | j vaut 0 | __main__ | ||
7 | 2 | 1 | j vaut 1 | __main__ |
On voit que :
- On indique seulement les lignes qui s'exécutent en vrai.
- Certaines lignes s'exécutent plusieurs fois, c'est le principe de la boucle!
- On appelle ce cheminement le flot de contrĂŽle.
Afin de valider une trace :
- Placer un point d'arrĂȘt sur la premiĂšre ligne qui s'exĂ©cute.
- Lancer l'exécution en mode débogage.
- Sauter de ligne en ligne pour voir si la trace est correcte.
Si on se rend à la fin de l'exécution et que toutes tes valeurs étaient bonnes, on a bien compris ce que fait le code.