编辑:看起来这是一个非常古老的错误"或者,实际上,功能。例如,参见this mail
我正在尝试理解Python范围规则。更确切地说,我认为我了解它们,但后来我发现了这段代码here:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
print(x)
print(y)
y = 1
func()
在Python 3.4中,输出为:
xlocal
ytop
如果我用函数替换内部类,那么它合理地给出了UnboundLocalError
。你能解释一下为什么它在课堂上表现出这种奇怪的方式吗?选择范围规则的原因是什么?
答案 0 :(得分:13)
TL; DR :此行为自Python 2.1 PEP 227: Nested Scopes以来就已存在,并且当时已知。如果在类体(例如y
)中指定了名称,则假定它是本地/全局变量;如果未分配给(x
),则它也可能指向闭包单元格。词法变量不会显示为类主体的本地/全局名称。
在Python 3.4上,dis.dis(func)
显示以下内容:
>>> dis.dis(func)
4 0 LOAD_CONST 1 ('xlocal')
3 STORE_DEREF 0 (x)
5 6 LOAD_CONST 2 ('ylocal')
9 STORE_FAST 0 (y)
6 12 LOAD_BUILD_CLASS
13 LOAD_CLOSURE 0 (x)
16 BUILD_TUPLE 1
19 LOAD_CONST 3 (<code object C at 0x7f083c9bbf60, file "test.py", line 6>)
22 LOAD_CONST 4 ('C')
25 MAKE_CLOSURE 0
28 LOAD_CONST 4 ('C')
31 CALL_FUNCTION 2 (2 positional, 0 keyword pair)
34 STORE_FAST 1 (C)
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
LOAD_BUILD_CLASS
加载堆栈上的builtins.__build_class__
;这是用参数__build_class__(func, name)
调用的;其中func
是类正文,name
是'C'
。类主体是函数func
的常量#3:
>>> dis.dis(func.__code__.co_consts[3])
6 0 LOAD_NAME 0 (__name__)
3 STORE_NAME 1 (__module__)
6 LOAD_CONST 0 ('func.<locals>.C')
9 STORE_NAME 2 (__qualname__)
7 12 LOAD_NAME 3 (print)
15 LOAD_CLASSDEREF 0 (x)
18 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
21 POP_TOP
8 22 LOAD_NAME 3 (print)
25 LOAD_NAME 4 (y)
28 CALL_FUNCTION 1 (1 positional, 0 keyword pair)
31 POP_TOP
9 32 LOAD_CONST 1 (1)
35 STORE_NAME 4 (y)
38 LOAD_CONST 2 (None)
41 RETURN_VALUE
在课堂正文中,使用x
(15)访问LOAD_CLASSDEREF
,y
加载LOAD_NAME
(25)。 LOAD_CLASSDEREF
是一个Python 3.4+操作码,用于从闭包单元格中加载值,特别是在类体中(在以前的版本中,使用了泛型LOAD_DEREF
); LOAD_NAME
用于从 locals 加载值,然后加载 globals 。然而,封闭细胞既不是局部也不是全局。
现在,因为名称y
存储在类主体(35)中,所以它一直被用作闭包单元而不是本地/全局名称。
闭包单元格不会显示为类主体的局部变量。
此行为属实ever since implementing PEP 227 - nested scopes。然后BDFL指出这不应该被修复 - 因此它已经存在了13年以上。
自PEP 227以来唯一的变化是在Python 3中添加了nonlocal
;如果在类体内使用它,则类体可以设置包含范围内的单元格的值:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
nonlocal y # y here now refers to the outer variable
print(x)
print(y)
y = 1
print(y)
print(C.y)
func()
现在的输出是
xlocal
ylocal
1
Traceback (most recent call last):
File "test.py", line 15, in <module>
func()
File "test.py", line 13, in func
print(C.y)
AttributeError: type object 'C' has no attribute 'y'
即,print(y)
读取包含范围的单元格y
的值,y = 1
设置该单元格中的值;在这种情况下,没有为类C
创建任何属性。
答案 1 :(得分:7)
首先关注闭包的情况 - 函数中的函数:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
def inner():
# global y
print(x)
print(y)
y='inner y'
print(y)
inner()
请注意global
中注释掉的inner
如果你运行它,它会复制你得到的UnboundLocalError
。为什么?
在其上运行dis.dis:
>>> import dis
>>> dis.dis(func)
6 0 LOAD_CONST 1 ('xlocal')
3 STORE_DEREF 0 (x)
7 6 LOAD_CONST 2 ('ylocal')
9 STORE_FAST 0 (y)
8 12 LOAD_CLOSURE 0 (x)
15 BUILD_TUPLE 1
18 LOAD_CONST 3 (<code object inner at 0x101500270, file "Untitled 3.py", line 8>)
21 LOAD_CONST 4 ('func.<locals>.inner')
24 MAKE_CLOSURE 0
27 STORE_FAST 1 (inner)
14 30 LOAD_FAST 1 (inner)
33 CALL_FUNCTION 0 (0 positional, 0 keyword pair)
36 POP_TOP
37 LOAD_CONST 0 (None)
40 RETURN_VALUE
请注意x
内y
与func
的不同访问模式。在y='inner y'
内使用inner
创建了UnboundLocalError
现在取消注释global y
内的inner
。现在,您明确地创建y
作为顶级全球版本,直到辞职为y='inner y'
取消注释global
后,打印:
xlocal
ytop
inner y
你可以获得更合理的结果:
x = "xtop"
y = "ytop"
def func():
global y, x
print(x,y)
x = "xlocal"
y = "ylocal"
def inner():
global y
print(x,y)
y = 'inner y'
print(x,y)
inner()
打印:
xtop ytop
xlocal ylocal
xlocal inner y
闭包类的分析因实例与类变量以及正在执行裸体类(没有实例)的时间而变得复杂。
底线是相同的:如果您引用本地命名空间之外的名称,然后在本地分配相同的名称,您会得到一个令人惊讶的结果。
&#39;修复&#39;是相同的:使用全局关键字:
x = "xtop"
y = "ytop"
def func():
global x, y
x = "xlocal"
y = "ylocal"
class Inner:
print(x, y)
y = 'Inner_y'
print(x, y)
打印:
xlocal ylocal
xlocal Inner_y
您可以在PEP 3104
中阅读有关Python 3范围规则的更多信息