使用Python的eval()时,本地变量和全局变量有什么区别? [英] What is the difference between locals and globals when using Python's eval()?

查看:112
本文介绍了使用Python的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 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆