阅读documentation我发现了以下段落:
范围定义块中名称的可见性。如果是当地的 变量在块中定义,其范围包括该块。如果 定义发生在功能块中,范围扩展到任何块 包含在定义的内容中,除非包含的块引入 名称的不同绑定。 a中定义的名称范围 class block仅限于class block;它没有扩展到 方法的代码块 - 这包括理解和生成器 表达式,因为它们是使用函数作用域实现的。
我决定尝试自己从方法中访问类变量:
>>> 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
问题是为什么该语言的开发人员使方法中的类变量不可见?它背后的理由是什么?
答案 0 :(得分:9)
类块是用于构建字典的语法糖,然后将其传递给元类(通常为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包含包含属性的对象。除了函数中的局部变量之外,实际上没有任何“变量”的概念(可以嵌套以创建范围链)。将裸名称作为局部变量查找,然后在外部作用域(来自函数)中查找。使用点名称语法在其他对象上查找属性,并始终指定要查看的对象。
有一个用于解析属性引用的协议,该协议表示在attribute
上找不到obj
时,可以通过查看obj.attribute
类来解析obj
(及其基类,使用方法解析顺序)。这实际上是如何找到方法的;在您的示例中执行a.f()
时,a
对象不包含任何属性f
,因此会搜索a
的类(A
),并且找到方法定义。
在所有方法的外部作用域中自动提供类属性会很奇怪,因为没有其他属性以这种方式工作。它也有以下缺点:
thing
而不是Class.thing
或self.thing
。这使得它们看起来像模块全局变量而不是它们(方法定义通常很短,你很容易看到它们没有在本地定义)。self
上查找属性可以让它们更好地使用子类,因为它允许子类覆盖属性。对于“类常量”来说,这可能不是什么大不了的事,但对静态方法和类方法来说这非常重要。这些是我看到的主要原因,但最终它只是Python设计者的选择。你发现你没有这种隐含的引用类变量的能力是很奇怪的,但我发现C ++和Java等语言中的隐式类和实例变量访问很奇怪。不同的人有不同的意见。
答案 1 :(得分:8)
这似乎与使用显式self
参数有关,并且要求所有方法调用和实例属性访问明确使用self
。如果将类范围函数作为普通函数访问的不常见情况比通过self
作为方法访问它的常见情况要容易得多,那将至少是奇怪的。通常也可以通过Python中的实例访问类变量。
相反,在C ++中,类范围在所有方法中都是可见的,但调用方法隐式传递this
。这似乎是另一个理智的选择。