在exec中赋值的变量出现NameError,尽管它存在于locals中。

6 浏览
0 Comments

在exec中赋值的变量出现NameError,尽管它存在于locals中。

以下代码在Python2Python3中输出不同结果:

from sys import version
print(version)
def execute(a, st):
    b = 42
    exec("b = {}\nprint('b:', b)".format(st))
    print(b)
a = 1.
execute(a, "1.E6*a")

Python2输出:

2.7.2 (default, Jun 12 2011, 15:08:59) [MSC v.1500 32 bit (Intel)]
('b:', 1000000.0)
1000000.0

Python3输出:

3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42

为什么Python2execute函数中的变量b绑定到exec函数字符串中的值,而Python3不这样做?我如何在Python3中实现Python2的行为?我已经尝试在Python3中向exec函数传递全局和本地的字典,但是到目前为止都没有起作用。

--- 编辑 ---

阅读Martijns的回答后,我进一步分析了这个问题。在以下示例中,我将locals()字典作为d传递给exec,但是d['b']打印出的结果与仅打印b不同。

from sys import version
print(version)
def execute(a, st):
    b = 42
    d = locals()
    exec("b = {}\nprint('b:', b)".format(st), globals(), d)
    print(b)                     # 这将打印出42
    print(d['b'])                # 这将打印出1000000.0
    print(id(d) == id(locals())) # 这将打印出True
a = 1.
execute(a, "1.E6*a")
3.2.3 (default, Apr 11 2012, 07:15:24) [MSC v.1500 32 bit (Intel)]
b: 1000000.0
42
1000000.0
True

比较dlocals()的id,显示它们是同一个对象。但在这种情况下,b应该与d['b']相同。我的示例中有什么问题?

0
0 Comments

在Python 2和Python 3中,exec的用法有很大的区别。在Python 2中,将exec视为一个函数,但实际上它是Python 2中的一个语句。由于这个区别,在Python 3中使用exec不能改变函数作用域中的局部变量,即使在Python 2中是可以的,甚至不能改变之前声明的变量。

在Python 2中,使用exec语句意味着编译器知道要关闭局部作用域的优化(例如,从LOAD_FAST切换到LOAD_NAME,以在局部和全局作用域中查找变量)。而exec()作为一个函数,不再有这个选项,函数作用域现在始终被优化。

此外,在Python 2中,exec语句会使用PyFrame_LocalsToFastlocals()中找到的所有变量显式复制回函数局部变量,但前提是没有提供globalslocals参数。

正确的解决方法是为exec()调用使用一个新的命名空间(一个字典):

def execute(a, st):
    namespace = {}
    exec("b = {}\nprint('b:', b)".format(st), namespace)
    print(namespace['b'])

exec()文档非常明确地说明了这个限制:

注意:默认的locals与函数locals()的描述相同:不应尝试修改默认的locals字典。如果需要在函数exec()返回后查看代码对局部变量的影响,请传递一个显式的locals字典。

在这段对话中,还提到了一个问题,就是为什么在最后一个例子中,print(b)输出42,而print(d['b'])输出1000000.0,但它们的id是相等的:id(d) == id(locals())输出True

其中的解释是locals()的返回值是单向的,该映射可以被修改,但函数的真实局部变量不会受到影响。

Python 3不允许修改函数作用域内的局部变量,解决方法是使用一个新的命名空间来调用exec()

0