问题在这篇文章的末尾。
第一个片段:空本地变量字典。
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
>>>
问题:
请有人帮我理解这一点。
答案 0 :(得分:23)
在Python中,除非另外指定(使用global
语句或3.0+中的nonlocal
语句),如果修改变量,则变量在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
模块文档列出了function
,code
以及其他“幕后”对象的所有重要成员。
使用dis
模块查看outer
和inner
的字节码也可能会有所帮助。***例如,如果您运行this code,那么'我会看到本地LOAD_FAST
,单元格LOAD_DEREF
和全局LOAD_GLOBAL
。
但是,如果你真的想要了解所有这些是如何运作的,那么在{Eli Bendersky的“Python内部”博客上的symbol tables上的一系列文章几乎涵盖了所有内容。 (感谢Ashwini Chaudhary找到它并在评论中指出它。)
*这是在编译时检查的,而不是执行时间,所以试图将它与例如exec
混淆可能会成功地混淆Python和你自己。
**请注意,del
同时作为修改和访问。这可能会令人惊讶,但您可以看到def foo(): del x
会引发UnboundLocalError
因为del
使x
成为本地,而del
无法找到{{1}}价值。
*** ...假设你正在使用一个使用CPython样式字节码的Python实现,比如CPython本身(当然)或PyPy。
答案 1 :(得分:8)
Python通过查看在编译时如何使用变量来支持嵌套作用域。您在函数中分配的变量(或在函数中与import
绑定)被视为本地变量,其他所有变量都是非本地变量。尝试删除变量也会将其标记为本地变量。
在父作用域中搜索非本地名称,如果未找到则被视为全局名称。
在第二个示例中,x
引用父作用域中的名称。您没有分配它,因此它是一个嵌套名称,可以在本地命名空间中看到。它实际上不是 本地名称,而是一个自由变量;它的值取自父范围。
在上一个示例中,您尝试删除x
,使其成为本地名称。在分配任何内容之前尝试引用它会导致异常。
这些都记录在Python参考的Execution model文档中。具体做法是:
在代码块中使用名称时,使用最近的名称解析 封闭范围。代码块可见的所有此类范围的集合是 称为块的环境。
如果名称绑定在块中,则它是该块的局部变量。 如果名称在模块级别绑定,则它是全局变量。 (该 模块代码块的变量是本地的和全局的。)如果是 变量在代码块中使用但在那里没有定义,它是 free 变量
以下构造绑定名称:形式参数到函数,
import
语句,类和函数定义(这些绑定 定义块中的类或函数名称)和目标 标识符,如果出现在赋值中,for
循环标题中except
子句标题的第二个位置或者as
之后的第二个位置 声明。import
形式的from ... import *
语句绑定 导入模块中定义的所有名称,除了那些以 一个下划线。此表单只能在模块级别使用。
del
语句中出现的目标也被视为绑定 这个目的(虽然实际的语义是取消绑定名称)。它 取消绑定封闭范围引用的名称是非法的; 编译器将报告SyntaxError
。
答案 2 :(得分:0)
在python docs faq页面上有一个解释Why am I getting an UnboundLocalError when the variable has a value?,类似于abarnert和Martijn的上述答案。