Python类范围规则

iva*_*anl 28 python scoping

编辑:看起来这是一个非常古老的"错误"或实际上,功能.例如,参见这封邮件

我试图了解Python范围规则.更准确地说,我认为我理解它们,但后来我在这里找到了这个代码:

x = "xtop"
y = "ytop"
def func():
    x = "xlocal"
    y = "ylocal"
    class C:
        print(x)
        print(y)
        y = 1
func()
Run Code Online (Sandbox Code Playgroud)

在Python 3.4中,输出是:

xlocal
ytop
Run Code Online (Sandbox Code Playgroud)

如果我用函数替换内部类,那么它合理地给出了UnboundLocalError.你能解释一下为什么它在课堂上表现出这种奇怪的方式吗?选择范围规则的原因是什么?

Ant*_*ala 13

TL; DR:自Python 2.1 PEP 227:嵌套作用域以来,这种行为已经存在,并且当时就已为人所知.如果在类体(如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
Run Code Online (Sandbox Code Playgroud)

LOAD_BUILD_CLASS负载builtins.__build_class__在堆栈上; 这是用参数调用的__build_class__(func, name); func类的主体在哪里,name'C'.类主体是函数的常量#3 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
Run Code Online (Sandbox Code Playgroud)

在类体内,xLOAD_CLASSDEREF(15)访问,同时yLOAD_NAME(25)加载.这LOAD_CLASSDEREF是一个Python 3.4+操作码,用于从闭包单元格中加载值,特别是在类体中(在以前的版本中,使用了泛型LOAD_DEREF); 该LOAD_NAME是从装载值当地人,然后全局.然而,封闭细胞既不是局部也不是全局.

现在,因为名称y存储在类体(35)中,所以它一直被用作闭包单元而不是本地/全局名称.闭包单元格不会显示为类主体的局部变量.

自从实现PEP 227 - 嵌套作用域以来,这种行为一直如此.然后BDFL指出这不应该被修复 - 因此它已经存在了13年以上.


自PEP 227以来唯一的变化是nonlocal在Python 3中添加; 如果在类体内使用它,则类体可以设置包含范围内的单元格的值:

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()
Run Code Online (Sandbox Code Playgroud)

现在的输出是

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'
Run Code Online (Sandbox Code Playgroud)

也就是说,print(y)读取y包含范围的单元格y = 1的值,并设置该单元格中的值; 在这种情况下,没有为该类创建任何属性C.

  • 如果Guido说这不是一个bug,那么它就是一个功能.;) (4认同)

daw*_*awg 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()  
Run Code Online (Sandbox Code Playgroud)

注意注释掉globalinner.如果你运行它,它复制了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
Run Code Online (Sandbox Code Playgroud)

注意xvs y内部的不同访问模式func.y='inner y'内部的使用inner创造了UnboundLocalError

现在global y在内部取消注释inner.现在你明确地创建y了一个顶级的全局版本,直到辞职为止y='inner y'

使用未global注释的打印件:

xlocal
ytop
inner y
Run Code Online (Sandbox Code Playgroud)

可以通过以下方式获得更明智的结果:

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()    
Run Code Online (Sandbox Code Playgroud)

打印:

xtop ytop
xlocal ylocal
xlocal inner y
Run Code Online (Sandbox Code Playgroud)

闭包类的分析因实例与类变量以及正在执行裸体类(没有实例)的内容而变得复杂.

底线是相同的:如果您引用本地命名空间之外的名称,然后在本地分配相同的名称,您会得到一个令人惊讶的结果.

'fix'是相同的:使用global关键字:

x = "xtop"
y = "ytop"
def func():
    global x, y
    x = "xlocal"
    y = "ylocal"
    class Inner:
        print(x, y)
        y = 'Inner_y'
        print(x, y) 
Run Code Online (Sandbox Code Playgroud)

打印:

xlocal ylocal
xlocal Inner_y
Run Code Online (Sandbox Code Playgroud)

您可以在PEP 3104中阅读有关Python 3范围规则的更多信息

  • 我理解你指出的函数的行为.但是仍然不清楚_classes_表现出差异的原因是什么. (5认同)
  • PEP 3104仅引入了`nonlocal`语句,并没有实际解释行为.这种行为是长期存在的PEP 227副作用,3104从未改变过. (4认同)