Python类范围规则

时间:2015-03-29 18:48:47

标签: python scoping

编辑:看起来这是一个非常古老的错误"或者,实际上,功能。例如,参见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。你能解释一下为什么它在课堂上表现出这种奇怪的方式吗?选择范围规则的原因是什么?

2 个答案:

答案 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_CLASSDEREFy加载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

请注意xyfunc的不同访问模式。在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范围规则的更多信息