首页 > 解决方案 > Python中类和函数范围之间的行为差​​异

问题描述

您可能知道,变量的范围在 python 中是静态确定的(Python中局部变量和全局变量的规则是什么?)。

例如 :

a = "global"
def function():
    print(a)
    a = "local"

function()

# UnboundLocalError: local variable 'a' referenced before assignment

相同的规则适用于类,但它似乎默认为全局范围而不是引发AttributeError

a = "global"

class C():
    print(a)
    a = "local"

# 'global'

此外,在嵌套函数的情况下,行为是相同的(不使用nonlocalor global):

a = "global"

def outer_func():
    a = "outer"
    
    def inner_func():
        print(a)
        a = "local"
 
    inner_func()

outer_func()

# UnboundLocalError: local variable 'a' referenced before assignment

但是在嵌套类的情况下,它仍然默认为全局范围,而不是外部范围(同样不使用globalor nonlocal):

a = "global"

def outer_func():
    a = "outer"
    
    class InnerClass:
        print(a)
        a = "local"

outer_func()

# 'global'

最奇怪的部分是嵌套类在没有声明时默认为外部范围a

a = "global"

def outer_func():
    a = "outer"
    
    class InnerClass:
        print(a)

outer_func()

# 'outer'

所以我的问题是:

标签: pythonpython-3.x

解决方案


官方文档的第 9.2 节中给出了非常详细的答案。问题的关键是

...另一方面,名称的实际搜索是在运行时动态完成的——但是,语言定义正在向静态名称解析发展,在“编译”时,所以不要依赖动态名称解析!(实际上,局部变量已经是静态确定的。)

当您在类定义中时,在其执行时刻是最内部的范围,动态名称解析适用。因此,您会看到 的全局值的打印输出a

如果名称解析是静态的,就像在函数定义中一样,a即使在 print 语句中,该名称也会被识别为本地名称。这就是为什么你不能a在分配给它之前打印一个函数。

第 4.2.2 节提到了类主体范围的规则:

类定义块和参数在名称解析的上下文中是特殊的exec()eval()类定义是可以使用和定义名称的可执行语句。这些引用遵循名称解析的正常规则,但在全局命名空间中查找未绑定的局部变量除外。

让我们仔细解析最后一句,因为它完全涵盖了你的最后两个示例。首先,在这种情况下,什么是未绑定的局部变量?类体创建一个新的命名空间,就像输入一个函数一样。如果名称绑定在类主体的某处,则它是局部变量。如上所述,这是静态确定的。如果您尝试在首次绑定之前引用该名称,则您有一个未绑定的局部变量。python 不会像函数调用那样引发错误,而是直接跳转到全局命名空间来执行查找(并且也忽略内置函数)。在所有其他情况下(不是局部变量),正常的 LEGB 查找顺序适用。

这确实有点违反直觉,我认为它推动了,如果不是彻底打破了最不意外的规则。


推荐阅读