22

问题在这篇文章的末尾。

第一个片段:空的局部变量字典。

def outer():
    x = 1
    def inner():
        print "Local variables: %s" % locals()
    return inner()
print outer()

输出:局部变量:{}

第二个片段:在 inner() 函数内打印并创建局部变量条目。

def outer():
    x = 1
    def inner():
        print x
        print "Local variables: %s" % locals()
    return inner()
print outer()

输出:

1
Local variables: {'x': 1}

第三个片段:内部函数内部的 del x:

def outer():
    x = 1
    def inner():
        print x
        print "Local variables: %s" % locals()
        del x
    return inner()
print outer()

输出:

>>> outer()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 7, in outer
  File "<stdin>", line 4, in inner
UnboundLocalError: local variable 'x' referenced before assignment
>>>

问题 :

  1. 在第二个片段中,打印语句如何创建局部变量。
  2. 如果它在内部函数中创建局部变量,为什么我无法删除它。

有人可以帮我理解这一点。

4

3 回答 3

23

在 Python 中,除非您另外指定(使用global语句或nonlocal3.0+ 中的语句),locals否则如果您在函数中的任何位置修改(分配给它、del它等)变量,则该变量存在。*

在第一个片段中,您从不修改x,甚至访问它,因此它不是本地的。事实上,它甚至不存在。这很容易。

第二个版本是棘手的。x不是本地的inner,因为您没有在inner. 因此,Python 去寻找它,逐个作用域向外移动,直到找到具有该变量的作用域。它发现它是一个局部变量outer。这意味着它是一个闭包变量或自由变量inner由于该locals函数包括闭包变量以及局部变量,因此您可以看到它。

第三个版本,通过做del x,使x本地到inner.** 所以,它出现在locals. print但是,您在没有分配任何东西的情况下尝试它,所以还没有任何价值。所以你得到一个UnboundLocalError.

一般来说,一旦你理解了 Python 在这里试图完成的基本思想,你通常很清楚你有什么样的变量。但如果不清楚,详细规则在Naming and Binding中定义。


如果你想了解闭包是如何在幕后工作的,你可以从检查函数对象开始。尝试这个:

def outer():
    x = 1
    def inner():
        print x
        print "Local variables: %s" % locals()
    return inner
inner = outer()
print inner.func_closure
print inner.func_code.co_freevars
print outer.func_code.co_cellvars

模块文档列出了、和其他“幕后”对象的所有inspect重要成员。functioncode

使用dis模块查看字节码outer并且inner也可能会有所帮助。*** 例如,如果您运行此代码,您将看到LOAD_FAST本地、LOAD_DEREF单元和LOAD_GLOBAL全局的 a。

但是,如果您真的想了解所有这些是如何工作的,那么 Eli Bendersky 的“Python 内部”博客上关于符号表的系列文章几乎涵盖了所有内容。(感谢 Ashwini Chaudhary 找到它并在评论中指出它。)


* 这是在编译时检查的,而不是在执行时检查的,所以试图混淆它,例如,exec可以成功地混淆 Python 和你自己。

** 请注意,del这既算作修改,也算作访问。这可能令人惊讶,但您可以看到这def foo(): del x会引发 an,UnboundLocalError因为delmake xlocal 并且同样del无法找到值。

*** … 假设您使用的是使用 CPython 样式字节码的 Python 实现,例如 CPython 本身(当然)或 PyPy。

于 2014-01-13T12:18:39.513 回答
8

Python 通过查看在编译时如何使用变量来支持嵌套范围。您在函数中分配的变量(或与函数中的 an 绑定import)被认为是本地的,其他一切都是非本地的。尝试删除变量也会将其标记为本地变量。

在父范围内搜索非本地名称,如果未找到,则将其视为全局名称。

在您的第二个示例中,x指的是父范围中的名称。您没有分配给它,所以它是一个嵌套名称,并且可以在本地命名空间中看到。它实际上不是一个本地名称,而是一个自由变量;它的值取自父范围。

在上一个示例中,您尝试删除x,使其成为本地名称。在分配任何内容之前尝试引用它会导致异常。

这都记录在Python 参考的执行模型文档中。具体来说:

在代码块中使用名称时,将使用最近的封闭范围对其进行解析。对代码块可见的所有此类范围的集合称为代码块的环境

如果名称绑定在块中,则它是该块的局部变量。如果名称绑定在模块级别,则它是一个全局变量。(模块代码块的变量是局部的和全局的。)如果一个变量在一个代码块中使用但没有在那里定义,它是一个自由变量

以下构造绑定名称:函数、 import语句、类和函数定义的形式参数(这些在定义块中绑定类或函数名称),以及如果出现在赋值、for循环头、在第二个位置作为标识符的目标with 语句中的except子句标题或之后as。该import形式的语句from ... import *绑定了导入模块中定义的所有名称,以下划线开头的名称除外。此表格只能在模块级别使用。

出现在del语句中的目标也被认为是为此目的绑定的(尽管实际语义是取消绑定名称)。取消绑定被封闭范围引用的名称是非法的;编译器将报告一个SyntaxError.

于 2014-01-13T12:19:23.830 回答
0

有一个解释为什么当变量有值时我得到一个 UnboundLocalError ?在 python 文档常见问题页面上,类似于 abarnert 和 Martijn 的上述答案。

于 2014-01-14T02:44:15.373 回答