使用Python';s eval()?

使用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)

如果变量作为全局变量或局部变量传递给Python函数,为什么会有区别

同样,如果没有明确给出,Python将把
\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
的字典。