使用Python';s eval()?
如果变量作为全局变量或局部变量传递给Python函数,为什么会有区别 同样,如果没有明确给出,Python将把使用Python';s eval()?,python,eval,Python,Eval,如果变量作为全局变量或局部变量传递给Python函数,为什么会有区别 同样,如果没有明确给出,Python将把\uuuuuu内置项\uuuuu复制到globals。但肯定还有其他一些我看不到的区别 考虑下面的示例函数。它接受一个字符串code,并返回一个函数对象。不允许使用内置函数(例如abs()),但允许使用math软件包中的所有函数 def make_fn(code): import math ALLOWED_LOCALS = {v:getattr(math, v)
\uuuuuu内置项\uuuuu
复制到globals。但肯定还有其他一些我看不到的区别
考虑下面的示例函数。它接受一个字符串code
,并返回一个函数对象。不允许使用内置函数(例如abs()
),但允许使用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)
它按预期工作,不使用任何本地或全局对象:
fn = make_fn('x + 3')
fn(5) # outputs 8
但使用math
函数时,它不起作用:
fn = make_fn('cos(x)')
fn(5)
>>> 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
这将输出以下异常:
<string> in <lambda>(x)
NameError: global name 'cos' is not defined
与上述示例相同:
fn = make_fn('cos(x)')
fn(5) # outputs 0.28366218546322625
这里具体发生了什么?Python默认情况下将名称查找为全局名称;只有在函数中指定给的名称才会作为局部变量进行查找(因此,作为函数参数或在函数中指定给的任何名称) 当您使用
dis.dis()
函数反编译代码对象或函数时,可以看到这一点:
fn = make_fn('cos(x)')
fn(5)
>>> 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
将cos
加载为全局名称,仅查看全局名称空间。LOAD\u FAST
操作码使用当前名称空间(函数本地名称空间)按索引查找名称(函数本地名称空间经过高度优化并存储为C数组)
还有三个操作码用于查找名称LOAD_CONST
(保留为真常量,如None
和不可变值的文字定义),LOAD_DEREF
(引用闭包)和LOAD_NAME
。后者同时考虑局部变量和全局变量,仅在函数代码对象无法优化时使用,因为LOAD\u NAME
要慢得多
如果你真的想在locals
中查找cos
,你必须强制代码被取消选择;这只适用于Python 2中,通过添加exec()
调用(或exec
语句):
现在,LOAD\u NAME
用于cos
,因为所有Python都知道,exec()
调用将该名称添加为本地名称
即使在这种情况下,
LOAD\u NAME
查找的局部变量将是函数本身的局部变量,而不是传递给eval
的局部变量,这些局部变量仅用于父作用域。在文档中说“存在全局字典,并且缺少“内置项”。我猜这意味着键“内置”不存在,但在您的示例中,您将其设置为“无”。我建议您通过del ALLOWED['builtins']更改ALLOWED['builtins']=None,然后重试。@jorispilot我的问题未连接到内置。我刚才提到了它,因为eval有一个特殊的globals外壳。如果您想禁止内置函数,例如abs
,那么我将内置函数设置为None的方法是正确的cos
不是一个内置函数,它是math
模块的一部分。我没有真正理解你的答案。考虑函数:<代码> DEF fn():a=1;在本地打印“a”,在本地打印“a”;在globals()中打印“a”,在globals()中打印“a”;return(lambda:a)(-为什么这样做,而不是eval('lambda:a',{},{'a':1})(
),因为每个作用域都有自己的局部变量eval
是父作用域,lambda
得到它自己的作用域,它自己的locals()
。与第一个函数中的情况相同,不是吗a
是fn
的局部变量,然后用于lambda函数。在第二个示例中,a
在eval
的局部变量中,然后在lambda函数中使用。不,它不是。对不起,我应该看得更深一点。编译器在fn
中将a
标记为闭包,然后嵌套lambda引用该闭包(LOAD\u DEREF
)。不能使用eval()
和本地字典构建闭包;未绑定的a
被标记为全局,没有将a
作为局部的父函数作用域。编译时不查阅传递到eval
的字典。