类块中定义的名称范围不会扩展到方法块。这是为啥?

Posted

技术标签:

【中文标题】类块中定义的名称范围不会扩展到方法块。这是为啥?【英文标题】:The scope of names defined in class block doesn't extend to the methods' blocks. Why is that?类块中定义的名称范围不会扩展到方法块。这是为什么? 【发布时间】:2012-03-19 08:15:01 【问题描述】:

阅读documentation我遇到了以下段落:

范围定义名称在块中的可见性。如果一个本地 变量在块中定义,其范围包括该块。如果 定义发生在功能块中,范围扩展到任何块 包含在定义中,除非包含的块引入 名称的不同绑定。 a 中定义的名称范围 类块仅限于类块;它不延伸到 方法的代码块 - 这包括理解和生成器 表达式,因为它们是使用函数范围实现的。

我决定自己尝试从方法中访问类变量:

>>> class A():
    i = 1
    def f(self):
        print(i)            

>>> a = A()

>>> a.i
1

>>> a.f()
Traceback (most recent call last):
  File "<pyshell#7>", line 1, in <module>
    a.f()
  File "<pyshell#4>", line 4, in f
    print(i)
NameError: global name 'i' is not defined

我知道变量i可以通过显式指向类名A.i来访问:

>>> a = A()
>>> class A():
    i = 1
    def f(self):
        print(A.i)          
>>> a = A()
>>> a.f()
1

问题是为什么语言的开发者让类变量在方法中不可见?其背后的原理是什么?

【问题讨论】:

【参考方案1】:

类块是构建字典的语法糖,然后将其传递给元类(通常为type)以构造类对象。

class A:
    i = 1
    def f(self):
        print(i)

大致相当于:

def f(self):
    print(i)
attributes = 'f': f, 'i': 1)
A = type('A', (object,) attributes)

这样看来,i 名称没有来自外部范围。但是,显然有一个临时范围供您执行类块中的语句。该类块可能脱糖成类似的东西:

def attributes():
    i = 1
    def f(self):
        print(i)
    return locals()
A = type('A', (object,), attributes())

在这种情况下,对i 的外部引用将起作用。然而,这将与 Python 的对象系统哲学“背道而驰”。

Python 有对象,其中包含属性。除了函数中的局部变量(可以嵌套以创建范围链)之外,实际上没有任何“变量”的概念。裸名被查找为局部变量,然后在外部范围(来自函数)中查找。 属性在其他对象上使用点分名称语法进行查找,并且您始终指定要查找的对象。

有一个解析属性引用的协议,它说当attributeobj上找不到时,obj.attribute可以通过查看obj的类(及其基类,使用方法解析顺序)。这实际上是如何找到方法的;在您的示例中执行a.f()时,a对象不包含f属性,因此搜索a的类(即A),并找到方法定义。

在所有方法的外部范围内自动提供类属性会很奇怪,因为没有其他属性以这种方式工作。它还会有以下缺点:

    在类外部定义并稍后分配给它的函数必须使用与定义为类的一部分的函数不同的语法来引用类属性。 因为它更短,它会鼓励引用类属性包括静态方法和类方法作为裸名:thing,而不是使用Class.thingself.thing。这使得它们看起来像模块全局变量,而实际上它们不是(方法定义通常足够短,您可以很容易地看到它们不是在本地定义的)。 请注意,在self 上查找属性可以让它们更好地与子类一起使用,因为它允许子类覆盖该属性。这对于“类常量”可能没什么大不了的,但对于静态方法和类方法来说却非常重要。

这些是我看到的主要原因,但最终这只是 Python 的设计者做出的选择。您会觉得很奇怪,您没有这种隐式引用类变量的能力,但我发现 C++ 和 Java 等语言中的隐式类和实例变量访问很奇怪。不同的人有不同的看法。

【讨论】:

哦,***,与不正确、模棱两可的答案相比,需要较长时间才能回答的正确答案的投票率更低。我希望 O.P. 能意识到这比投票率更高的答案更多。 @jsbueno:在我的回答中指出究竟是什么不正确会更具建设性。 (我很快就会对此答案发表更多评论。) @jsbueno 仅仅因为我说得更久并不意味着 Sven 的回答不正确。我完全同意他建议的原因,我只是建议了更多。我的想法也是猜测,因为正如 Sven 指出的那样,Python 以这种方式工作没有严格的技术要求;为什么 Python 应该 以这种方式工作的论点总是至少是一方主观的。 @Sven Python 中的作用域是词法的(我绝对同意替代方案是疯狂的)。但概念上程序员不会将类作用域视为词法作用域。他们只是认为“方法隐式地可以访问类属性”。因此,在类之外定义然后分配给它的函数具有不同的范围并不奇怪,但这会令人困惑!关于对象哲学的东西只是 attributes 总是从特定对象中检索,而不是在范围内隐式可用。隐式范围内的类属性会违背这一点。 生成器表达式创建范围的事实在此特定上下文中也相关,因为它会导致 strange anomaly,这将是 not 跳过类范围的参数在名称查找期间。【参考方案2】:

这似乎与使用显式self 参数有关,并且要求所有方法调用和实例属性访问显式使用self。如果将类作用域函数作为普通函数访问的不常见情况比通过self 作为方法访问它的常见情况要容易得多,这至少会很奇怪。类变量通常也可以通过 Python 中的实例来访问。

相比之下,在 C++ 中,类范围在所有方法中都是可见的,但调用方法会隐式传递 this。这似乎是另一个明智的选择。

【讨论】:

是的。我知道了。所以他们试图强制使用self 来访问类变量。是否允许从方法中访问类变量会产生其他影响? @ovgolovin:好吧,让我们等待其他人的解释。也许有人找到了一个好的网络链接。 实际上,这是胡思乱想的猜测”- 检查 Ben 的回答以了解 Python 中发生了什么。 @jsbueno:这是 Python 中的设计决策。没有真正的技术原因,原因相当“软”。因此,任何答案都必须做一些猜测,除非您找到 Guido 解释他为什么以他的方式做事的来源。 Ben 的回答中感知到的技术原因在很大程度上是错误的,尽管他给出了一些很好的软理由,特别是与子类化的交互。

以上是关于类块中定义的名称范围不会扩展到方法块。这是为啥?的主要内容,如果未能解决你的问题,请参考以下文章

为啥条件块中的函数声明在 Chrome 而不是 Firefox 中提升到函数范围?

为啥我的代码在完成块中运行两次

Python名称绑定和作用域的关系??

自定义 UISlider 不会结束

企业库日志记录块中的扩展属性

java 为啥数组这里要用大括号?