使用Python的eval()时,本地变量和全局变量有什么区别? [英] What is the difference between locals and globals when using Python's eval()?
问题描述
如果将变量作为全局变量或局部变量传递给Python函数 eval(),为什么这样做会有所不同?
文档化,如果未明确给出,Python会将 __ builtins __
复制到全局变量。但是还必须有其他一些我看不到的区别。
考虑以下示例函数。它接受字符串 code
并返回一个函数对象。不允许使用内置函数(例如 abs()
),但是 math
包中的所有函数都可以。
def make_fn(code):
导入数学
ALLOWED_LOCALS = {v:getattr(math,v)
for v in filter(lambda x:not x.startswith('_'),dir(math))
}
return eval('lambda x:%s'%code,{'__builtins__':无},ALLOWED_LOCALS)
它不使用任何本地或全局对象即可正常工作:
fn = make_fn('x + 3')
fn(5)#输出8
但是使用数学
函数不起作用:
fn = make_fn('cos(x)')
fn(5)
这将输出以下异常:
< string>在< lambda>(x)
NameError中:未定义全局名称'cos'
但是当传递与全局变量相同的映射时,它将起作用:
def make_fn(code):
导入数学
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,{})
与上面相同的示例:
fn = make_fn('cos(x)')
fn(5)#输出0.28366218546322625
这里发生了什么?
Python默认将名称查找为全局变量;只有在功能中分配的名称才被视为本地名称(因此,任何作为函数参数或在函数中分配的名称)。
使用 dis.dis()
函数反编译代码对象或函数时,您会看到以下内容:
>> import dis
>> def func(x):
...返回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
将 cos
加载为全局名称,仅在globals命名空间中查找。 LOAD_FAST
操作码使用当前名称空间(函数本地名称)通过索引查找名称(功能本地名称空间经过高度优化并存储为C数组)。
还有另外三个操作码可以查找名称; LOAD_CONST
(保留真实常量,例如 None
和不可变值的文字定义), LOAD_DEREF
(引用闭包)和 LOAD_NAME
。后者会同时查看局部变量和全局变量,并且仅在无法优化功能代码对象时使用,因为 LOAD_NAME
慢得多。
如果您真的想要在 locals $ c $中查找
cos
c>,您必须强制对代码进行优化;通过在 exec()
调用(或 exec
语句):
>> def unoptimized(x):
... exec('pass')
...返回cos(x)
...
>> dis.dis(未优化)
2 0 LOAD_CONST 1('通过')
3 LOAD_CONST 0(无)
6 DUP_TOP
7 EXEC_STMT
3 8 LOAD_NAME 0(cos)
11 LOAD_FAST 0(x)
14 CALL_FUNCTION 1
17 RETURN_VALUE
现在 LOAD_NAME
用于 cos
,因为对于所有Python来说, exec()
调用将该名称添加为本地名称。
即使在这种情况下,本地名称 LOAD_NAME
将会是函数本身的局部变量,而不是传递给 eval
的局部变量,后者仅用于父范围。 / p>
Why does it make a difference if variables are passed as globals or as locals to Python's function eval()?
As also described in the documenation, Python will copy __builtins__
to globals, if not given explicitly. But there must be also some other difference which I cannot see.
Consider the following example function. It takes a string code
and returns a function object. Builtins are not allowed (e.g. abs()
), but all functions from the math
package.
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)
It works as expected not using any local or global objects:
fn = make_fn('x + 3')
fn(5) # outputs 8
But it does not work using the math
functions:
fn = make_fn('cos(x)')
fn(5)
This outputs the following exception:
<string> in <lambda>(x)
NameError: global name 'cos' is not defined
But when passing the same mapping as globals it works:
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, {})
Same example as above:
fn = make_fn('cos(x)')
fn(5) # outputs 0.28366218546322625
What happens here in detail?
Python looks up names as globals by default; only names assigned to in functions are looked up as locals (so any name that is a parameter to the function or was assigned to in the function).
You can see this when you use the dis.dis()
function to 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
as a global name, only looking in the globals namespace. The LOAD_FAST
opcode uses the current namespace (function locals) to look up names by index (function local namespaces are highly optimized and stored as a C array).
There are three more opcodes to look up names; LOAD_CONST
(reserved for true constants, such as None
and literal definitions for immutable values), LOAD_DEREF
(to reference a closure) and LOAD_NAME
. The latter does look at both locals and globals and is only used when a function code object could not be optimized, as LOAD_NAME
is a lot slower.
If you really wanted cos
to be looked up in locals
, you'd have to force the code to be unoptimised; this only works in Python 2, by adding a 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
is used for cos
because for all Python knows, the exec()
call added that name as a local.
Even in this case, the locals LOAD_NAME
looks into, will be the locals of the function itself, and not the locals passed to eval
, which are for only for the parent scope.
这篇关于使用Python的eval()时,本地变量和全局变量有什么区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!