Une règle de prix tapée par la gérante
Un loueur de vélos laisse sa gérante taper une règle de prix, comme tarif * jours + caution, dans une page de réglages. La règle change chaque semaine.
Personne ne veut recompiler et relancer le site à chaque fois. Le site garde donc l’arbre syntaxique de la règle et le calcule, nœud par nœud, à chaque réservation.
À la fin de cette leçon
Remarque
Je peux exécuter directement un arbre syntaxique.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Interpréteur | un programme qui en exécute un autre sans produire de code machine | |
| Nœud | un point de l’arbre | BinOp |
| Évaluer | calculer la valeur d’un nœud | |
| Feuille | un nœud sans rien en dessous | Constant(value=8) |
| Environnement | les noms accessibles et leurs valeurs | {'prix': 4} |
| Liaison | un nom associé à sa valeur |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Python vous montre son arbre
Python est aussi un compilateur. Avant d’exécuter une ligne, il la découpe en jetons et en fait un arbre syntaxique. Le module ast affiche cet arbre pour une ligne reçue par un tube. --mode eval précise que la ligne est une seule expression.
Chaque nœud a un nom. Une feuille n’a rien en dessous. Comparez les trois arbres affichés.
L’arbre de 8 - 2 - 1
echo "8 - 2 - 1" | python3 -m ast --mode evalAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un arbre avec des noms
echo "remise * (prix - 3)" | python3 -m ast --mode evalAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un moins placé devant
echo "-solde" | python3 -m ast --mode evalAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : les nœuds affichés par Python
| Nœud | Ce que c’est | Sa valeur vient de |
|---|---|---|
| Constant(value=8) | Une feuille qui contient un nombre | Le nombre lui-même |
| Name(id='prix') | Une feuille qui contient un nom | La recherche du nom |
| BinOp(left, op, right) | Une opération à deux côtés | Les deux côtés, puis l’opération |
| UnaryOp(op, operand) | Une opération à un seul côté | Ce côté, puis l’opération |
| Sub(), Mult(), USub() | Quelle opération | Rien : c’est une étiquette |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Les enfants d’abord, puis le nœud
Un interpréteur qui parcourt l’arbre est une fonction qui évalue un nœud. Une feuille qui contient un nombre rend ce nombre. Une opération évalue d’abord son enfant gauche et son enfant droit. Elle fait ensuite son propre calcul sur les deux réponses.
Comme un enfant peut être un arbre entier, la fonction s’appelle elle-même. La démo traite + et * sur des nombres.
Un évaluateur qui parcourt l’arbre
# Évaluer un arbre : un nombre, ou [signe, gauche, droite].
def evaluer(arbre):
if type(arbre) == int:
return arbre
signe = arbre[0]
# On calcule d'abord les deux côtés : c'est la récursion.
gauche = evaluer(arbre[1])
droite = evaluer(arbre[2])
if signe == "+":
return gauche + droite
if signe == "*":
return gauche * droite
return None
arbre = ["*", ["+", 2, 3], 4]
print(evaluer(arbre))
# Remplacez l'arbre par ["+", 2, ["*", 3, 4]], puis relancez.
Erreur fréquente
Attention
Beaucoup de gens pensent qu’un interpréteur traduit l’arbre en code machine en secret, puis le lance. Un interpréteur qui parcourt l’arbre ne le fait jamais. Il visite les nœuds et fait le travail en passant, de nouveau à chaque exécution. C’est pourquoi il est plus lent qu’un code compilé, et pourquoi il démarre tout de suite.
Les noms se cherchent dans un environnement
Une feuille qui contient un nom a besoin d’une valeur. L’interpréteur tient un environnement : chaque nom accessible, avec sa valeur. Chaque paire est une liaison.
La fonction eval de Python reçoit l’environnement sous forme de dictionnaire. Donnez-lui un second dictionnaire, et Python le fouille en premier. C’est ainsi qu’une portée intérieure cache un nom extérieur.
Évaluer avec un environnement
python3 -c "print(eval('prix * qte', {'prix': 4, 'qte': 5}))"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Oublier un nom
python3 -c "print(eval('prix * qte', {'prix': 4}))"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un environnement intérieur qui change qte
python3 -c "print(eval('prix * qte', {'prix': 4, 'qte': 5}, {'qte': 2}))"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Un environnement intérieur qui change prix
python3 -c "print(eval('prix * qte', {'prix': 4, 'qte': 5}, {'prix': 10}))"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Exemple travaillé : chercher prix * qte
| Run | prix trouvé dans | qte trouvé dans | Affiché |
|---|---|---|---|
| Le premier | Le seul dictionnaire : 4 | Le seul dictionnaire : 5 | 20 |
| Le deuxième | Le seul dictionnaire : 4 | Nulle part | NameError |
| Le troisième | L’extérieur : 4 | L’intérieur : 2 | 8 |
| Le quatrième | L’intérieur : 10 | L’extérieur : 5 | 50 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Pour aller plus loin
Ajoutez un nœud si à votre évaluateur : ["si", test, alors, sinon]. Évaluez le test. S’il ne vaut pas 0, évaluez alors, sinon évaluez sinon.
Vérifiez qu’une seule des deux branches est évaluée. Écrivez un test qui le prouve, avec une branche qui lèverait NameError si elle s’exécutait.