使用Python的eval()时locals和globals有什么区别?

lum*_*ric 12 python eval

如果将变量作为全局变量或本地变量传递给Python的函数eval(),为什么会有所不同?

正如文档中描述的__builtins__,如果没有明确给出,Python将复制到全局变量.但也必须有一些我看不到的其他差异.

请考虑以下示例函数.它需要一个字符串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)
Run Code Online (Sandbox Code Playgroud)

它按预期工作,不使用任何本地或全局对象:

   fn = make_fn('x + 3')
   fn(5) # outputs 8
Run Code Online (Sandbox Code Playgroud)

但它无法使用这些math功能:

   fn = make_fn('cos(x)')
   fn(5)
Run Code Online (Sandbox Code Playgroud)

这会输出以下异常:

   <string> in <lambda>(x)
   NameError: global name 'cos' is not defined
Run Code Online (Sandbox Code Playgroud)

但是当传递与globals相同的映射时,它可以工作:

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, {})
Run Code Online (Sandbox Code Playgroud)

与上面相同的例子:

   fn = make_fn('cos(x)')
   fn(5) # outputs 0.28366218546322625
Run Code Online (Sandbox Code Playgroud)

这里有什么细节?

Mar*_*ers 10

Python默认将名称查找为全局变量; 只有在函数中分配的名称才会被查找为本地符号(因此任何名称都是函数的参数或在函数中赋值).

当您使用该dis.dis()函数反编译代码对象或函数时,您可以看到这一点:

>>> 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        
Run Code Online (Sandbox Code Playgroud)

LOAD_GLOBAL加载cos为全局名称,仅查看全局命名空间.该LOAD_FAST操作码使用当前命名空间(功能当地人)通过指数(功能本地名称空间的高度优化的和为C数组存储的)查找名称.

查找名称还有三个操作码; LOAD_CONST(保留为真常量,例如None不可变值的文字定义),LOAD_DEREF(引用闭包)和LOAD_NAME.后者确实查看本地和全局,并且仅在无法优化函数代码对象时使用,因为LOAD_NAME速度要慢很多.

如果你真的cos被抬头看locals,你必须强迫代码不被优化; 这只是工作在Python 2,通过添加exec()调用(或exec声明):

>>> 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        
Run Code Online (Sandbox Code Playgroud)

现在LOAD_NAME用于cos因为所有Python都知道,该exec()调用将该名称添加为本地.

即使在这种情况下,本地人LOAD_NAME查看,将是函数本身的本地,而不是传递给的本地eval,这仅适用于父作用域.