如果将变量作为全局变量或本地变量传递给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
,这仅适用于父作用域.
归档时间: |
|
查看次数: |
2861 次 |
最近记录: |