2.34. Code dynamique¶
Trois fonctions natives prennent une chaîne de code source Python et l’exécutent : eval(), exec() et compile(). Ensemble, elles permettent au code de construire et d’exécuter davantage de code à l’exécution – ce qui est parfois exactement le bon outil, et bien plus souvent une source de bogues et de failles de sécurité.
Avertissement
Ces fonctions exécutent du code Python arbitraire. Passer une entrée utilisateur à eval ou exec – une chaîne issue d’un fichier que l’utilisateur peut modifier, une charge utile reçue par le réseau, une valeur saisie à une invite REPL – permet à cette entrée de faire tout ce que le script appelant pourrait faire, jusqu’à supprimer tous les fichiers de l’appareil. Utilisez-les délibérément, jamais au sein de code exécuté automatiquement, et jamais sur des données que vous ne contrôlez pas.
2.34.1. eval¶
eval() exécute une seule expression et renvoie sa valeur :
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
Par défaut, l’expression voit les variables globales et locales de l’appelant, c’est pourquoi name est résolu dans le second exemple. Passer des dictionnaires explicites permet de placer l’évaluation dans un bac à sable :
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Même dans un bac à sable, eval est dangereux. Il existe des techniques bien connues pour s’échapper de tels bacs à sable ; ne vous fiez pas à la seule astuce du __builtins__ vide pour des entrées non fiables.
2.34.2. exec¶
exec() exécute un bloc de code plutôt qu’une seule expression – instructions, définitions de fonctions, boucles – et renvoie None :
exec("for i in range(3): print(i)")
Sortie
0
1
2
Le bloc peut définir des noms qui deviennent disponibles par la suite, avec quelques réserves concernant la portée locale par rapport à la portée globale. exec au sein d’une fonction se comporte rarement comme l’auteur s’y attend ; si vous en avez besoin, exécutez-le au niveau du module.
2.34.3. compile¶
compile() transforme une chaîne de code source en un objet code qui peut être passé ultérieurement à eval() ou exec(). Utilisez-le lorsque le même code source sera exécuté plusieurs fois – l’analyse syntaxique n’a lieu qu’une fois, l’exécution est plus rapide :
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Sortie
0
1
4
9
16
L’argument du milieu est une étiquette qui apparaît dans les traces d’appels si le code lève une exception. Le troisième argument est "eval" pour une seule expression, "exec" pour un bloc, ou "single" pour une instruction de style interactif qui affiche son résultat.
2.34.4. Quand recourir à ces fonctions¶
Presque jamais. La plupart des cas d’usage qu’imaginent les débutants ont des alternatives plus sûres :
Lire un fichier de configuration. Utilisez
json– des données structurées, sans exécution.Évaluer une valeur numérique saisie par l’utilisateur. Utilisez
int()/float()pour l’analyser, puis effectuez l’arithmétique. Si l’utilisateur a réellement besoin de saisir une formule, utilisez un petit analyseur d’expressions, paseval.
Lorsque vous avez réellement besoin de eval / exec / compile, isolez le site d’appel, journalisez la chaîne exacte qui est sur le point d’être exécutée, et traitez le code source comme la chose la plus suspecte de votre programme.