问题 为什么Python 3 exec()在指定本地时失败?


以下在Python 3中执行时没有错误:

code = """
import math

def func(x):
    return math.sin(x)

func(10)
"""
_globals = {}
exec(code, _globals)

但是,如果我尝试捕获局部变量dict,它也会失败 NameError

>>> _globals, _locals = {}, {}
>>> exec(code, _globals, _locals)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-9-aeda81bf0af1> in <module>()
----> 1 exec(code, {}, {})

<string> in <module>()

<string> in func(x)

NameError: name 'math' is not defined

为什么会发生这种情况,如何在捕获全局变量和局部变量时执行此代码?


3230
2017-09-22 19:39


起源



答案:


来自 exec() 文件

请记住,在模块级别,全局变量和本地变量是相同的字典。如果 exec 得到两个单独的对象 全局 和 当地人,代码将被执行,就像它嵌入在类定义中一样。

您传入了两个单独的词典,但尝试执行需要模块范围全局变量的代码。 import math 在课堂上会产生一个 本地范围属性,并且您创建的函数将无法访问它,因为类范围名称不考虑函数闭包。

看到 命名和绑定 在Python执行模型中引用:

类定义块和参数 exec() 和 eval() 在名称解析的背景下是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的常规规则,但在全局命名空间中查找未绑定的局部变量。类定义的名称空间成为类的属性字典。类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块[。]

您可以通过尝试在类定义中执行代码来重现错误:

>>> class Demo:
...     import math
...     def func(x):
...         return math.sin(x)
...     func(10)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in Demo
  File "<stdin>", line 4, in func
NameError: name 'math' is not defined

只是传入  字典。


11
2017-09-22 19:44



你能解开短语“好像它嵌入在一个类定义中”的含义吗?和/或给出一个具体的例子,说明“类范围名称不考虑功能关闭”? - jez
@jez:看 解析名称: 类定义块和参数 exec() 和 eval() 在名称解析的背景下是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的常规规则,但在全局命名空间中查找未绑定的局部变量。 - Martijn Pieters♦
可能值得明确指出,此处看到的行为与在类定义中定义方法时所看到的行为完全相同(当前答案确实如此,但从不使用“方法”一词),并重复从在课程定义中强调的问题。另外,一个巧妙的技巧是通过将其转换为闭包来使其运行: exec("def _dummy():\n {}\n_dummy()".format("\n ".join(code.strip().splitlines())), {}, {}) - ncoghlan
@ncoghlan:从技术上讲,你在一个类中定义函数。他们只 成为 在实例上查找它们时触发描述符协议以绑定它们的方法。 - Martijn Pieters♦
@Martijn:我把那句话加到了 exec 文档。你完美地解释了它。 - Terry Jan Reedy


答案:


来自 exec() 文件

请记住,在模块级别,全局变量和本地变量是相同的字典。如果 exec 得到两个单独的对象 全局 和 当地人,代码将被执行,就像它嵌入在类定义中一样。

您传入了两个单独的词典,但尝试执行需要模块范围全局变量的代码。 import math 在课堂上会产生一个 本地范围属性,并且您创建的函数将无法访问它,因为类范围名称不考虑函数闭包。

看到 命名和绑定 在Python执行模型中引用:

类定义块和参数 exec() 和 eval() 在名称解析的背景下是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的常规规则,但在全局命名空间中查找未绑定的局部变量。类定义的名称空间成为类的属性字典。类块中定义的名称范围仅限于类块;它没有扩展到方法的代码块[。]

您可以通过尝试在类定义中执行代码来重现错误:

>>> class Demo:
...     import math
...     def func(x):
...         return math.sin(x)
...     func(10)
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in Demo
  File "<stdin>", line 4, in func
NameError: name 'math' is not defined

只是传入  字典。


11
2017-09-22 19:44



你能解开短语“好像它嵌入在一个类定义中”的含义吗?和/或给出一个具体的例子,说明“类范围名称不考虑功能关闭”? - jez
@jez:看 解析名称: 类定义块和参数 exec() 和 eval() 在名称解析的背景下是特殊的。类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的常规规则,但在全局命名空间中查找未绑定的局部变量。 - Martijn Pieters♦
可能值得明确指出,此处看到的行为与在类定义中定义方法时所看到的行为完全相同(当前答案确实如此,但从不使用“方法”一词),并重复从在课程定义中强调的问题。另外,一个巧妙的技巧是通过将其转换为闭包来使其运行: exec("def _dummy():\n {}\n_dummy()".format("\n ".join(code.strip().splitlines())), {}, {}) - ncoghlan
@ncoghlan:从技术上讲,你在一个类中定义函数。他们只 成为 在实例上查找它们时触发描述符协议以绑定它们的方法。 - Martijn Pieters♦
@Martijn:我把那句话加到了 exec 文档。你完美地解释了它。 - Terry Jan Reedy