Python中eval函数的作用域

20

考虑以下示例:

i=7
j=8
k=10
def test():
    i=1
    j=2
    k=3
    return dict((name,eval(name)) for name in ['i','j','k'])

它返回:

>>> test()
{'i': 7, 'k': 10, 'j': 8}

为什么 eval 不考虑函数内定义的变量?根据文档,可以选择性地传递 globals 和 locals 字典。这是什么意思?最后,我该如何修改这个小例子让它能工作?


3
你可以在函数内部的变量声明前添加 global 来修改它的工作方式,但这是一个不好的方法。另一方面,通常使用 eval 也是不好的做法。 - Rusty
除非你确定必须使用 eval,否则要远离它。Rusty所说的就是这个意思。 - l4mpi
@l4mpi 我知道使用 eval 是个坏主意,但我只是在玩作用域,而且我不理解这种行为。 - Pierpaolo
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
13

生成器被实现为函数作用域:(参见Python官方文档)

类块中定义的名称范围仅限于类块本身,不会扩展到方法的代码块中——这包括生成器表达式,因为它们是使用函数作用域实现的。

因此,在dict()构造函数内部的生成器具有其自己的locals()字典。现在让我们来看一下Py_eval的源代码,特别是当globals()locals()都为None时:

if (globals == Py_None) {
        globals = PyEval_GetGlobals();
        if (locals == Py_None)
            locals = PyEval_GetLocals();
    }
所以对于你的例子,当循环执行时,PyEval_GetLocals() 将为空,而 globals() 将是全局字典。请注意,在函数内部定义的 ijk 不在生成器的本地作用域中,而是在其封闭作用域中。
>>> dict((name,eval(name, globals(), {})) for name in ['i', 'j', 'k'])
{'i': 7, 'k': 10, 'j': 8}

4
这是因为生成器表达式函数有不同的作用域造成的:
>>> def test():
    i, j, k = range(1, 4)
    return dict((j, locals()) for _ in range(i))

>>> test()
{2: {'.0': <listiterator object at 0x02F50A10>, 'j': 2, '_': 0}}
在该范围内使用 j 会将其从函数绑定,因为这是最近封闭的范围,但是 ik 没有被局部绑定(因为 k 没有被引用,而 i 只用于创建 range)。
请注意,您可以通过以下方式避免此问题:
return dict(i=i, j=j, k=k)
或者使用字典字面值:
return {'i': i, 'j': j, 'k': k}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,