【问题标题】:Behaviour discrepancy between classes and functions scope in PythonPython中类和函数范围之间的行为差​​异
【发布时间】:2021-06-22 03:05:58
【问题描述】:

您可能知道,变量的范围在 python (What are the rules for local and global variables in 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'

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

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

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

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'

所以我的问题是:

  • 为什么函数和类之间存在差异(一个引发异常,另一个默认为全局范围。
  • 在嵌套类中,为什么在使用之后定义的变量时,默认范围必须变为全局范围,而不是继续使用外部范围?

【问题讨论】:

  • 这几乎就是它应该表现的样子。你读过哪些文档?

标签: python python-3.x


【解决方案1】:

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

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

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

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

Section 4.2.2 中提到了类主体作用域的规则:

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

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

这确实有点违反直觉,我认为如果不是完全违反最小意外规则,它也会推动。

【讨论】:

  • 谢谢!它确实解释了函数和类之间的区别,但是从外部作用域切换到全局作用域的需要呢?
  • @QuentinCoumes 已更新。但转念一想,我的答案是垃圾
  • 正如我上一个示例所示,只要不重新定义变量,内部类似乎就知道封闭范围。无论如何,感谢您提供更多信息。
  • @QuentinCoumes。我想我现在更新了正确的解释。
  • 我们最近有a similar question,我想知道为什么会出现这个异常。我发现的“最佳”解释来自 Guido,他解释说这是为了与 Python 2.1 兼容。不过,我还没有找到,为什么当时会出现这种行为......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-03-21
  • 2013-04-12
  • 2011-08-13
  • 1970-01-01
  • 2021-09-17
  • 1970-01-01
  • 2015-02-24
相关资源
最近更新 更多