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

时间:2012-02-29 20:19:39

标签: python oop class scope

阅读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

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

2 个答案:

答案 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),并且找到方法定义。

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

  1. 在类之外定义并在以后分配给它的函数必须使用不同的语法来引用类属性,而不是定义为类的一部分的函数。
  2. 因为它更短,所以会鼓励引用类属性,包括staticmethods和classmethods 作为裸名:thing而不是Class.thingself.thing。这使得它们看起来像模块全局变量而不是它们(方法定义通常很短,你很容易看到它们没有在本地定义)。
  3. 请注意,在self上查找属性可以让它们更好地使用子类,因为它允许子类覆盖属性。对于“类常量”来说,这可能不是什么大不了的事,但对静态方法和类方法来说这非常重要。
  4. 这些是我看到的主要原因,但最终它只是Python设计者的选择。你发现你没有这种隐含的引用类变量的能力是很奇怪的,但我发现C ++和Java等语言中的隐式类和实例变量访问很奇怪。不同的人有不同的意见。

答案 1 :(得分:8)

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

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