Python class 范围规则
Python class scoping rules
编辑: 看起来这是一个非常古老的 "bug" 或者,实际上是一个功能。参见,例如 this mail
我正在尝试了解 Python 范围规则。更准确地说,我认为我了解它们,但后来我发现了这段代码 here:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
print(x)
print(y)
y = 1
func()
在Python3.4中输出为:
xlocal
ytop
如果我用一个函数替换内部 class 那么它合理地给出 UnboundLocalError
。你能解释一下为什么它在 classes 中表现出这种奇怪的方式吗?选择这种范围规则的原因是什么?
首先关注闭包的情况——函数中的函数:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
def inner():
# global y
print(x)
print(y)
y='inner y'
print(y)
inner()
注意 inner
中注释掉的 global
如果你 运行 这个,它会复制你得到的 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
与 func
内部的 y
的不同访问模式。在 inner
中使用 y='inner y'
创建了 UnboundLocalError
现在取消注释 inner
中的 global y
。现在您已明确创建 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
闭包 class 的分析因实例与 class 变量以及什么/何时执行裸体 class(没有实例)而变得复杂。
底线是相同的:如果您在本地命名空间之外引用一个名称,然后在本地分配给相同的名称,您会得到一个令人惊讶的结果。
与'fix'相同:使用全局关键字:
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 个范围规则的更多信息
TL;DR:此行为自 Python 2.1 PEP 227: Nested Scopes 以来就存在,并且在当时就已为人所知。如果在 class 主体中分配了一个名称(如 y
),则假定它是一个 local/global 变量;如果它没有分配给 (x
),那么它也可能指向一个闭包单元。词法变量不会显示为 local/global 名称到 class 正文。
在 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
是 class 正文,name
是 'C'
。 class 主体是函数 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
在 class 主体中,x
通过 LOAD_CLASSDEREF
(15) 访问,而 y
通过 LOAD_NAME
(25) 加载。 LOAD_CLASSDEREF
是一个 Python 3.4+ 操作码,用于从闭包单元格中加载值,特别是在 class 主体内(在以前的版本中,使用了通用的 LOAD_DEREF
); LOAD_NAME
用于从 locals 加载值,然后是 globals。然而,闭包单元既不显示为局部变量也不显示为全局变量。
现在,因为名称 y
存储在 class 正文 (35) 中,所以它一直被用作 local/global 名称而不是闭包单元。
闭包单元不会显示为 class 主体的局部变量。
这种行为是真实的 ever since implementing PEP 227 - nested scopes。当时 BDFL 表示这不应该被修复 - 因此这已经持续了 13 年以上。
自 PEP 227 以来的唯一变化是在 Python 3 中添加了 nonlocal
;如果在 class 正文中使用它,则 class 正文可以设置包含范围内的单元格的值:
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
在该单元格中设置值;在这种情况下,没有为 class C
.
创建属性
编辑: 看起来这是一个非常古老的 "bug" 或者,实际上是一个功能。参见,例如 this mail
我正在尝试了解 Python 范围规则。更准确地说,我认为我了解它们,但后来我发现了这段代码 here:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
print(x)
print(y)
y = 1
func()
在Python3.4中输出为:
xlocal
ytop
如果我用一个函数替换内部 class 那么它合理地给出 UnboundLocalError
。你能解释一下为什么它在 classes 中表现出这种奇怪的方式吗?选择这种范围规则的原因是什么?
首先关注闭包的情况——函数中的函数:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
def inner():
# global y
print(x)
print(y)
y='inner y'
print(y)
inner()
注意 inner
中注释掉的 global
如果你 运行 这个,它会复制你得到的 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
与 func
内部的 y
的不同访问模式。在 inner
中使用 y='inner y'
创建了 UnboundLocalError
现在取消注释 inner
中的 global y
。现在您已明确创建 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
闭包 class 的分析因实例与 class 变量以及什么/何时执行裸体 class(没有实例)而变得复杂。
底线是相同的:如果您在本地命名空间之外引用一个名称,然后在本地分配给相同的名称,您会得到一个令人惊讶的结果。
与'fix'相同:使用全局关键字:
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 个范围规则的更多信息TL;DR:此行为自 Python 2.1 PEP 227: Nested Scopes 以来就存在,并且在当时就已为人所知。如果在 class 主体中分配了一个名称(如 y
),则假定它是一个 local/global 变量;如果它没有分配给 (x
),那么它也可能指向一个闭包单元。词法变量不会显示为 local/global 名称到 class 正文。
在 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
是 class 正文,name
是 'C'
。 class 主体是函数 func
:
>>> 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
在 class 主体中,x
通过 LOAD_CLASSDEREF
(15) 访问,而 y
通过 LOAD_NAME
(25) 加载。 LOAD_CLASSDEREF
是一个 Python 3.4+ 操作码,用于从闭包单元格中加载值,特别是在 class 主体内(在以前的版本中,使用了通用的 LOAD_DEREF
); LOAD_NAME
用于从 locals 加载值,然后是 globals。然而,闭包单元既不显示为局部变量也不显示为全局变量。
现在,因为名称 y
存储在 class 正文 (35) 中,所以它一直被用作 local/global 名称而不是闭包单元。
闭包单元不会显示为 class 主体的局部变量。
这种行为是真实的 ever since implementing PEP 227 - nested scopes。当时 BDFL 表示这不应该被修复 - 因此这已经持续了 13 年以上。
自 PEP 227 以来的唯一变化是在 Python 3 中添加了 nonlocal
;如果在 class 正文中使用它,则 class 正文可以设置包含范围内的单元格的值:
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
在该单元格中设置值;在这种情况下,没有为 class C
.