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

ovg*_*vin 10 python oop scope class

阅读文档我遇到了以下段落:

范围定义块内名称的可见性.如果在块中定义了局部变量,则其范围包括该块.如果定义发生在功能块中,则作用域将扩展到定义块中包含的任何块,除非包含的块为名称引入了不同的绑定.类块中定义的名称范围仅限于类块; 它没有扩展到方法的代码块 - 这包括了解和生成器表达式,因为它们是使用函数作用域实现的.

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

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

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

>>> a = A()
>>> class A():
    i = 1
    def f(self):
        print(A.i)          
>>> a = A()
>>> a.f()
1
Run Code Online (Sandbox Code Playgroud)

问题是为什么该语言的开发人员使方法中的类变量不可见?它背后的理由是什么?

Ben*_*Ben 10

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

class A:
    i = 1
    def f(self):
        print(i)
Run Code Online (Sandbox Code Playgroud)

大致相当于:

def f(self):
    print(i)
attributes = {'f': f, 'i': 1)
A = type('A', (object,) attributes)
Run Code Online (Sandbox Code Playgroud)

从这个角度来看,这个i名称没有外部范围.但是,显然有一个临时的范围可供您在类块中执行语句.这个类块可能会更像是:

def attributes():
    i = 1
    def f(self):
        print(i)
    return locals()
A = type('A', (object,), attributes())
Run Code Online (Sandbox Code Playgroud)

在这种情况下,外部引用i将起作用.然而,这将违背Python的对象系统哲学.

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

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

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

  1. 在类之外定义并在以后分配给它的函数必须使用不同的语法来引用类属性,而不是定义为类的一部分的函数.
  2. 因为它更短,所以它会鼓励引用类属性,包括staticmethods和classmethods作为裸名:thing而不是使用Class.thingself.thing.这使它们看起来像模块全局变量而不是它们(方法定义通常很短,你可以很容易地看到它们没有在本地定义).
  3. 请注意,查找属性self允许它们更好地使用子类,因为它允许子类覆盖属性.对于"类常量"来说,这可能不是什么大不了的事,但它对静态方法和类方法非常重要.

这些是我看到的主要原因,但最终它只是Python设计者的选择.你发现你没有这种隐含的引用类变量的能力是很奇怪的,但我发现C++和Java等语言中的隐式类和实例变量访问很奇怪.不同的人有不同的意见.

  • 哦,stackoverflow,需要更长的时间才能回答的正确答案的投票率低于不正确、模糊的答案,这些答案是在安静的情况下写出来的。我希望OP能够意识到这比投票率较高的答案更像是一个答案。 (2认同)
  • @jsbueno仅仅因为我更长时间的表达并不意味着斯文的答案是不正确的.我完全赞同他建议的理由,我只是提出了一些建议.我的想法也是猜测,因为正如斯文所指出的那样,没有艰难的技术*要求让Python以这种方式工作; 为什么Python*应该以这种方式工作的论据总是至少是主观的. (2认同)

Sve*_*ach 7

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

相反,在C++中,类范围在所有方法中都是可见的,但调用方法是隐式传递的this.这似乎是另一个理智的选择.

  • @jsbueno:这是Python中的*设计决策*.没有真正的技术原因,原因相当"软".所以任何答案都需要做一些猜测,除非你找到一个来源,Guido解释了为什么他按照他的方式做事.Ben的答案中出现的技术原因在很大程度上是错误的,尽管他给出了一些很好的软性原因,特别是与子类化的交互. (2认同)