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, non eval.

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.