2.34. Dynamische code¶
Drie ingebouwde functies nemen een string met Python-broncode en voeren die uit: eval(), exec() en compile(). Samen laten ze code toe om tijdens runtime meer code te construeren en uit te voeren – wat soms precies het juiste gereedschap is, en veel vaker een bron van bugs en beveiligingslekken.
Waarschuwing
Deze functies voeren willekeurige Python uit. Gebruikersinvoer doorgeven aan eval of exec – een string uit een bestand dat de gebruiker kan bewerken, een payload ontvangen over het netwerk, een waarde getypt op een REPL-prompt – laat die invoer alles doen wat het aanroepende script zou kunnen doen, tot en met het verwijderen van elk bestand op het apparaat. Gebruik ze weloverwogen, nooit binnen code die automatisch draait, en nooit op gegevens die je niet beheert.
2.34.1. eval¶
eval() voert een enkele expressie uit en retourneert de waarde ervan:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
De expressie ziet standaard de globals en locals van de aanroeper, wat de reden is waarom name in het tweede voorbeeld wordt opgelost. Door expliciete dictionaries door te geven kun je de evaluatie sandboxen:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Zelfs gesandboxt is eval gevaarlijk. Er bestaan welbekende technieken om uit zulke sandboxes te ontsnappen; vertrouw voor niet-vertrouwde invoer niet alleen op de truc met de lege __builtins__.
2.34.2. exec¶
exec() voert een blok code uit in plaats van een enkele expressie – statements, functiedefinities, lussen – en retourneert None:
exec("for i in range(3): print(i)")
Uitvoer:
0
1
2
Het blok kan namen definiëren die daarna beschikbaar worden, met enkele kanttekeningen over lokale versus globale scope. exec binnen een functie gedraagt zich zelden zoals de schrijver verwacht; als je het nodig hebt, voer het dan uit op moduleniveau.
2.34.3. compile¶
compile() zet een bronstring om in een code-object dat later aan eval() of exec() kan worden doorgegeven. Gebruik het wanneer dezelfde broncode vele keren zal draaien – het parsen gebeurt eenmaal, de uitvoering is sneller:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Uitvoer:
0
1
4
9
16
Het middelste argument is een label dat in tracebacks verschijnt als de code een fout opwerpt. Het derde argument is "eval" voor een enkele expressie, "exec" voor een blok, of "single" voor een interactief statement dat zijn resultaat afdrukt.
2.34.4. Wanneer je hiernaar moet grijpen¶
Bijna nooit. De meeste use cases die beginners zich voorstellen hebben veiligere alternatieven:
Een configuratiebestand lezen. Gebruik
json– gestructureerde gegevens, geen uitvoering.Een numerieke waarde evalueren die door de gebruiker is getypt. Gebruik
int()/float()om te parsen, daarna rekenkunde. Als de gebruiker echt een formule moet invoeren, gebruik dan een kleine expressieparser, geeneval.
Wanneer je eval / exec / compile wel nodig hebt, isoleer dan de aanroeplocatie, log de exacte string die op het punt staat te worden uitgevoerd, en behandel de broncode als het meest verdachte ding in je code.