2013-08-28 10 views
9

Perché fa una differenza se le variabili vengono passate come globali o come locals alla funzione di Python eval()?Qual è la differenza tra locali e globali quando si utilizza Eval() di Python?

Come anche described in the documenation, Python copierà __builtins__ a livello globale, se non fornito esplicitamente. Ma deve esserci anche qualche altra differenza che non riesco a vedere.

Considerare la seguente funzione di esempio. Prende una stringa code e restituisce un oggetto funzione. I build non sono consentiti (ad esempio abs()), ma tutte le funzioni dal pacchetto math.

def make_fn(code): 
    import math 
    ALLOWED_LOCALS = {v:getattr(math, v) 
     for v in filter(lambda x: not x.startswith('_'), dir(math)) 
    } 
    return eval('lambda x: %s' % code, {'__builtins__': None}, ALLOWED_LOCALS) 

Esso funziona come previsto non utilizzare alcun oggetto locale o globale:

fn = make_fn('x + 3') 
    fn(5) # outputs 8 

Ma non funziona utilizzando le math funzioni:

fn = make_fn('cos(x)') 
    fn(5) 

Emette la seguente eccezione:

<string> in <lambda>(x) 
    NameError: global name 'cos' is not defined 

Ma quando si passa la stessa mappatura come globali funziona:

def make_fn(code): 
    import math 
    ALLOWED = {v:getattr(math, v) 
     for v in filter(lambda x: not x.startswith('_'), dir(math)) 
    } 
    ALLOWED['__builtins__'] = None 
    return eval('lambda x: %s' % code, ALLOWED, {}) 

stesso esempio di sopra:

fn = make_fn('cos(x)') 
    fn(5) # outputs 0.28366218546322625 

Cosa succede qui in dettaglio?

+0

Nella documentazione si dice "il dizionario globale è presente e manca" __builtins__ "". Immagino che questo significhi che la chiave "__builtins__" non sia presente, ma nel tuo esempio la imposti su Nessuna. Ti suggerisco di cambiare CONSENTITO ['__ builtins__'] = Nessuno del DELOWED ['__ builtins__'] e riprovare. – jorispilot

+0

@jorispilot Il mio problema non è connesso a __builtins__. L'ho appena menzionato, perché c'è questo involucro speciale dei globali per eval. Il modo in cui ho impostato __builtins__ su None è corretto, se si desidera proibire funzioni incorporate come 'abs'. 'cos' non è una funzione costruita, fa parte del modulo' math'. – lumbric

risposta

8

Python cerca i nomi come globali per impostazione predefinita; solo i nomi assegnati alle funzioni vengono considerati come locals (quindi qualsiasi nome che è un parametro della funzione o che è stato assegnato alla funzione).

Si può vedere questo quando si utilizza la funzione dis.dis() di decompilare oggetti di codice o funzioni:

>>> import dis 
>>> def func(x): 
...  return cos(x) 
... 
>>> dis.dis(func) 
    2   0 LOAD_GLOBAL    0 (cos) 
       3 LOAD_FAST    0 (x) 
       6 CALL_FUNCTION   1 
       9 RETURN_VALUE   

LOAD_GLOBAL carichi cos come un nome globale, solo guardando nello spazio dei nomi globali. L'opcode LOAD_FAST utilizza lo spazio dei nomi corrente (locals delle funzioni) per cercare i nomi per indice (i namespace locali della funzione sono ottimizzati e memorizzati come array C).

Ci sono altri tre codici opzionali per cercare i nomi; LOAD_CONST (riservato per le costanti reali, come None e le definizioni letterali per i valori immutabili), LOAD_DEREF (per fare riferimento a una chiusura) e LOAD_NAME. Quest'ultimo guarda sia i locali che i globali e viene utilizzato solo quando non è possibile ottimizzare un oggetto codice funzione, poiché LOAD_NAME è molto più lento.

Se davvero voluto cos di essere guardato in locals, che avrebbe dovuto forzare il codice per essere unoptimised; questo solo opere in Python 2, con l'aggiunta di un exec() chiamata (o exec economico):

>>> def unoptimized(x): 
...  exec('pass') 
...  return cos(x) 
... 
>>> dis.dis(unoptimized) 
    2   0 LOAD_CONST    1 ('pass') 
       3 LOAD_CONST    0 (None) 
       6 DUP_TOP    
       7 EXEC_STMT   

    3   8 LOAD_NAME    0 (cos) 
      11 LOAD_FAST    0 (x) 
      14 CALL_FUNCTION   1 
      17 RETURN_VALUE   

Ora LOAD_NAME viene utilizzato per cos perché per tutti Python sa, la chiamata exec() aggiunto che nome come un locale.

Anche in questo caso, la gente del posto LOAD_NAME esamina, saranno i locali della funzione stessa, e non i locali passati a eval, che sono solo per l'ambito genitore.

+0

Non ho davvero capito la tua risposta. Considerare la funzione: 'def fn(): a = 1; stampa 'a in locals?', 'A' in locals(); stampa 'a in globals?', 'A' in globals(); return (lambda: a)() '- perché funziona, ma non' eval ('lambda: a', {}, {'a': 1})() '? – lumbric

+0

Poiché ogni ambito ha i propri * locali *. 'eval' è l'ambito genitore,' lambda' ha il suo ambito, il suo 'locals()'. –

+0

Stessa situazione della prima funzione, non è vero? 'a' è nella gente del posto di' fn' e quindi usato nella funzione lambda. Nel secondo esempio, 'a' si trova nella gente del posto di 'eval' e quindi usato nella funzione lambda. – lumbric

Problemi correlati