What is the difference between locals and globals when using Python's eval()? -
why make difference if variables passed globals or locals python's function eval()?
as described in documenation, python copy __builtins__
globals, if not given explicitly. there must other difference cannot see.
consider following example function. takes string code
, returns function object. builtins not allowed (e.g. abs()
), functions math
package.
def make_fn(code): import math allowed_locals = {v:getattr(math, v) v in filter(lambda x: not x.startswith('_'), dir(math)) } return eval('lambda x: %s' % code, {'__builtins__': none}, allowed_locals)
it works expected not using local or global objects:
fn = make_fn('x + 3') fn(5) # outputs 8
but not work using math
functions:
fn = make_fn('cos(x)') fn(5)
this outputs following exception:
<string> in <lambda>(x) nameerror: global name 'cos' not defined
but when passing same mapping globals works:
def make_fn(code): import math allowed = {v:getattr(math, v) v in filter(lambda x: not x.startswith('_'), dir(math)) } allowed['__builtins__'] = none return eval('lambda x: %s' % code, allowed, {})
same example above:
fn = make_fn('cos(x)') fn(5) # outputs 0.28366218546322625
what happens here in detail?
python looks names globals default; names assigned in functions looked locals (so name parameter function or assigned in function).
you can see when use dis.dis()
function decompile code objects or functions:
>>> 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
loads cos
global name, looking in globals namespace. load_fast
opcode uses current namespace (function locals) names index (function local namespaces highly optimized , stored c array).
there 3 more opcodes names; load_const
(reserved true constants, such none
, literal definitions immutable values), load_deref
(to reference closure) , load_name
. latter @ both locals , globals , used when function code object not optimized, load_name
lot slower.
if really wanted cos
looked in locals
, you'd have force code unoptimised; only works in python 2, adding exec()
call (or exec
statement):
>>> 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
now load_name
used cos
because python knows, exec()
call added name local.
even in case, locals load_name
looks into, locals of function itself, , not locals passed eval
, parent scope.
Comments
Post a Comment