【问题标题】:Why are module-level variables in a Python exec inaccessible?为什么 Python exec 中的模块级变量无法访问?
【发布时间】:2014-07-12 10:53:49
【问题描述】:

我正在尝试在 project 中使用 Python 的 exec 来执行嵌入的 Python 代码。

我遇到的问题是,在exec 语句中的模块级别 创建的变量无法从同一模块中定义的函数中访问。 p>

假设您有以下 Python 程序:

x = 5
def foo():
    print x
foo()

如果你把上面四行放在一个文件里运行,就没有问题了。

但是,如果您尝试在 exec 语句中运行同一段代码,它将不起作用。

这是我们之前的程序,在 exec 语句中:

import __builtin__

global_env = {'__builtins__': __builtin__}
local_env = dict()

exec """
x = 5
def foo():
    print x
foo()
""" in global_env, local_env

在执行时,它会产生以下错误,而不是正常工作:

Traceback (most recent call last):
  File "lab.py", line 94, in <module>
    """ in global_env, local_env
  File "<string>", line 5, in <module>
  File "<string>", line 4, in foo
NameError: global name 'x' is not defined

我认为模块级变量是全局存储的,但似乎至少在exec 中,它们不是。

例如,在前面的示例中,如果您将对 foo() 的调用替换为:

print global_env
print local_env

你得到:

{'__builtins__': <module '__builtin__' (built-in)>}
{'x': 5, 'foo': <function foo at 0x102c12938>}

因此,在模块级别定义的任何内容(包括x)都存储在locals() 中。

但是除了模块级别(exec 语句的)之外,不可能从任何地方访问x。特别是,正如我们在上面看到的,x 的本地范围对于在同一 exec 语句中定义的函数是不可见的。

解决方法

我找到了两种解决此问题的方法,并让 x 再次可访问。

第一个是在函数中使用global关键字:

exec """
x = 5
def foo():
    global x
    print x
foo()
""" in global_env, local_env

第二个是在exec 中为globals()locals() 使用相同的字典:

exec """
x = 5
def foo():
    print x
foo()
""" in global_env

但是,这些只是未解决原始问题的半修复/解决方法。

所以我的问题是:为什么 exec 中的模块级变量存储在本地,为什么除了模块级之外的任何地方都无法访问

一些密切相关的 StackOverflow 帖子:

【问题讨论】:

  • 您可能会充其量猜测“为什么”的问题。我从来没有见过 Guido 回答过这样的问题,即为什么某些东西在 Python 中是这样的,而且我认为其他核心开发人员通常也不会回答这些问题。

标签: python


【解决方案1】:

要了解发生了什么,您需要仔细阅读the docs。关键部分说:

如果将两个单独的对象作为全局对象和局部对象给出,则代码将像嵌入到类定义中一样被执行。

这意味着局部赋值会进入局部命名空间(相当于类级别的变量),但如果函数(即方法)尝试引用局部(类)变量,则它们不会成为闭包。

将您的代码与:

class Test(object):
    x = 1
    def foo():
        print x
    foo()

出于同样的原因,您会收到同样的错误。 foo 不是闭包,因此它尝试在全局命名空间中引用 x(不成功)。

【讨论】:

    【解决方案2】:

    你看到的行为是well documented:

    在所有情况下,如果省略可选部分,则执行代码 在当前范围内。如果只有 in 之后的第一个表达式是 指定,它应该是一个字典,将用于 全局变量和局部变量。如果给出两个表达式,它们是 分别用于全局变量和局部变量。如果提供, locals 可以是任何映射对象。请记住 在模块级别, globalslocals 是同一个字典。 如果两个单独的对象 以全局变量和局部变量的形式给出,代码将像执行 嵌入在类定义中。

    事实上:

    In [1]: class Test:
       ...:     x = 5
       ...:     def foo():
       ...:         print(x)
       ...:     foo()
       ...:     
    ---------------------------------------------------------------------------
    NameError                                 Traceback (most recent call last)
    <ipython-input-1-f20229bce3a1> in <module>()
    ----> 1 class Test:
          2     x = 5
          3     def foo():
          4         print(x)
          5     foo()
    
    <ipython-input-1-f20229bce3a1> in Test()
          3     def foo():
          4         print(x)
    ----> 5     foo()
          6 
    
    <ipython-input-1-f20229bce3a1> in foo()
          2     x = 5
          3     def foo():
    ----> 4         print(x)
          5     foo()
          6 
    
    NameError: name 'x' is not defined
    

    您看到的行为是预期的。如果您想像在模块级别一样执行代码,您必须对全局变量和局部变量使用相同的对象,因此您的解决方法就是您应该做的。

    【讨论】:

      【解决方案3】:

      您的问题包含x 是模块级变量的假设,但实际上它不是。

      在模块级别 globals()locals() 是相同的(这可能是特定于 CPython),而在函数范围内它们是不同的:

      def foo():
          print globals() is locals()
      
      print globals() is locals()
      foo()
      

      您指定范围的exec 语句不会在模块级别执行代码,它会在您告诉它执行的范围内执行它。因此,您的最后一个代码 sn-p 不是半修复或解决方法,它准确地解决了问题。

      当它出现在模块级别时,以下内容也适用。它与您的任何代码都有不同的含义,因为它分配给模块中的 xfoo,因此它确实使 x 成为模块级变量:

      exec '''
      x = 5
      def foo():
          print x
      foo()
      '''
      

      最后一个代码出现在函数中时不起作用的原因,以及为什么你的第一次尝试不起作用,这就是 Blckknght 的回答所说的。 exec 具有单独的作用域执行“就像在类定义中一样”,而不是“好像在模块中”或“好像在函数中”。这意味着您在其中定义的任何函数都不会访问它们周围的命名空间。它们访问自己的本地命名空间和 exec 语句的全局命名空间,这两者都不是定义 x 的位置。

      【讨论】:

        【解决方案4】:

        Python 的这个特性有一个有趣的结果。如果您在exec 语句中的模块级别 导入模块,则也无法从同一模块中定义的函数访问它。换句话说,由于答案中解释的相同原因,问题中的这个稍作修改的示例将不起作用:

        exec """
        import x
        def foo():
            print x
        foo()
        """ in global_env, local_env
        

        问题中提供的两种解决方法都可以用来解决这个问题

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2020-05-06
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-01-10
          相关资源
          最近更新 更多