2.34. Codice dinamico¶
Tre funzioni integrate accettano una stringa di codice sorgente Python e la eseguono: eval(), exec() e compile(). Insieme permettono al codice di costruire ed eseguire altro codice a runtime – il che è occasionalmente proprio lo strumento giusto, e molto più spesso fonte di bug e falle di sicurezza.
Avvertimento
Queste funzioni eseguono codice Python arbitrario. Passare input dell’utente a eval o exec – una stringa da un file che l’utente può modificare, un payload ricevuto dalla rete, un valore digitato al prompt del REPL – consente a quell’input di fare tutto ciò che lo script chiamante potrebbe fare, fino ad arrivare a eliminare ogni file sul dispositivo. Usale deliberatamente, mai all’interno di codice che viene eseguito automaticamente, e mai su dati che non controlli.
2.34.1. eval¶
eval() esegue una singola espressione e ne restituisce il valore:
>>> eval("3 * 7")
21
>>> name = "OpenMV"
>>> eval("name.lower()")
'openmv'
L’espressione vede per impostazione predefinita i globals e i locals del chiamante, ed è per questo che name viene risolto nel secondo esempio. Passare dizionari espliciti permette di isolare la valutazione in una sandbox:
eval("a + b", {"__builtins__": None}, {"a": 1, "b": 2})
Anche in sandbox, eval è pericolosa. Esistono tecniche ben note per evadere da tali sandbox; non affidarti al solo trucco di __builtins__ vuoto per input non attendibile.
2.34.2. exec¶
exec() esegue un blocco di codice anziché una singola espressione – istruzioni, definizioni di funzioni, cicli – e restituisce None:
exec("for i in range(3): print(i)")
Output:
0
1
2
Il blocco può definire nomi che diventano disponibili in seguito, con alcune avvertenze sullo scope locale rispetto a quello globale. exec all’interno di una funzione raramente si comporta come chi lo scrive si aspetta; se ne hai bisogno, eseguilo a livello di modulo.
2.34.3. compile¶
compile() trasforma una stringa sorgente in un oggetto codice che può essere passato in seguito a eval() o exec(). Usala quando lo stesso sorgente verrà eseguito molte volte – l’analisi avviene una sola volta, l’esecuzione è più veloce:
expr = compile("x * x", "<expr>", "eval")
for x in range(5):
print(eval(expr))
Output:
0
1
4
9
16
L’argomento centrale è un’etichetta che appare nei traceback se il codice solleva un’eccezione. Il terzo argomento è "eval" per una singola espressione, "exec" per un blocco, oppure "single" per un’istruzione in stile interattivo che stampa il suo risultato.
2.34.4. Quando ricorrere a queste funzioni¶
Quasi mai. La maggior parte dei casi d’uso che i principianti immaginano ha alternative più sicure:
Leggere un file di configurazione. Usa
json– dati strutturati, nessuna esecuzione.Valutare un valore numerico digitato dall’utente. Usa
int()/float()per il parsing, poi l’aritmetica. Se l’utente ha davvero bisogno di inserire una formula, usa un piccolo parser di espressioni, noneval.
Quando hai effettivamente bisogno di eval / exec / compile, isola il punto di chiamata, registra nel log la stringa esatta che sta per essere eseguita, e tratta il sorgente come la cosa più sospetta del tuo codice.