对 python 类 中的范围界定感到困惑

Confusion about scoping in python classes

我目前正在复习 python.org 的 python 教程。我来自 C++,在 Classes 教程 (https://docs.python.org/3/tutorial/classes.html) 中,我看到作用域与 C++ 中的相似。它说明了以下关于范围和嵌套的内容:

"在执行过程中的任何时候,至少有三个嵌套作用域的命名空间可以直接访问:
- 首先搜索的最内层范围包含本地名称
- 从最近的封闭范围开始搜索的任何封闭函数的范围包含非局部但也非全局名称
- 倒数第二个范围包含当前模块的全局名称
- 最外层的范围(最后搜索)是包含内置名称的名称空间“

但是我尝试使用同一页面中的以下代码:

class Dog:

    tricks = []             

    def __init__(self, name):
        self.name = name

    def add_trick(self, trick):
        self.tricks.append(trick)     #this is the troublesome self

>>> d = Dog('Fido')     
>>> e = Dog('Buddy')
>>> d.add_trick('roll over')    #without the self this complains
>>> e.add_trick('play dead')
>>> d.tricks                
['roll over', 'play dead']

如果我删除 self.tricks.append(trick) 中的 self 代码将不会编译并在调用函数 d.add_trick('roll over') 时抛出 NameError: name 'tricks' is not defined

为什么会这样?正如我从上面的段落中理解的那样,函数 add_trick 应该首先在它自己的本地范围内寻找一个名为 tricks 的变量,如果没有找到,则在最近的封闭范围内,这是Class Dog 的作用域,并且 它应该在那里找到它 ,而不需要使用 self。我错过了什么?

你的错误在于认为 class 是一个范围。它不是。正如您引用的文档所解释的那样,作用域是函数或模块。

正如教程所说,范围是按本地、非本地、全局、内置的顺序搜索的。

非局部作用域用于封闭函数。 class 声明不是函数。它的命名空间在用于创建 class 对象的 __dict__ 后被丢弃,因此 class 级别的变量不能在封闭函数中产生非局部变量。将 class 级别的赋值和变量读取视为对隐藏字典的隐式赋值和读取,而不是函数局部变量。 (Metaclasses 甚至可以用其他映射替换这个隐藏的字典。)

但是 class 范围确实添加了一个非本地 __class__。这很少直接使用,但对于 super().

的零参数形式很重要

这是 class 对象本身,因此在 class 声明完成执行之前它是未初始化的。因此,如果 主体执行后 __class__.tricks 被调用,__class__.tricks 将在方法内部工作(通常情况),但如果它被调用 during class 主体的执行。

Python 中还有其他需要注意的范围。 Comprehensions 像函数一样创建局部作用域。 (它们基本上像生成器函数一样编译 - 内部带有 yield 的那种。)此外,捕获的异常会在它们的处理子句末尾自动删除,以防止引用循环。

您可以使用 locals() 内置命令查看本地名称空间,使用 globals() 查看全局名称空间。内置范围只是内置模块。外地人很棘手。如果编译器发现它们被使用,它们实际上会出现在 locals() 中。函数对象保留对它们在 __closure__ 属性中使用的非局部变量的引用,该属性是单元格的元组。

这个例子有帮助吗:

In [27]: foobar = 'pre'                                                         
In [28]: class AClass(): 
    ...:     print('class def:', foobar) 
    ...:     foobar = 'class' 
    ...:     def __init__(self): 
    ...:         print('init:', foobar) 
    ...:         print(self.foobar) 
    ...:         self.foobar='instance' 
    ...:         print(self.foobar) 
    ...:                                                                        
class def: pre                  
In [29]: AClass.foobar                                            
Out[29]: 'class'

Class 定义就像 运行 一个函数。 foobar 最初是全局值,但随后被重新分配,现在作为 class 命名空间的一部分可用。

In [30]: x = AClass()                                                           
init: pre
class
instance
In [31]: x.foobar                                                               
Out[31]: 'instance'
In [32]: x.__class__.foobar                                                     
Out[32]: 'class'

当我们定义一个实例时,foobar 来自外部的全局命名空间。 self.foobar 最初访问 class 命名空间,但随后被重新定义为实例变量。

更改全局 foobar 反映在下一个实例创建中:

In [33]: foobar = 'post'                                                        
In [34]: y = AClass()                                                           
init: post
class
instance
In [35]: y.__class__.foobar                                                     
Out[35]: 'class'

您的教程页面,在 9.3.1. Class Definition Syntax 部分中说,在 class 定义中,有一个新的本地命名空间。那就是定义 foobar='class' 的地方。但是一旦超出 class 定义,该命名空间就会重命名为 AClass

实例创建在 class 定义之外运行。所以它 'sees' 全局 foobar,而不是 class 本地。它必须指定命名空间。

定义 class(或函数)和定义 'run' 时范围之间的区别可能令人困惑。

class 定义特别创建了一个 class 作用域,这是一种特殊的作用域。虽然 class 作用域确实包含在全局作用域中(class 变量可以访问全局变量),但 class 作用域不要包含其中的局部作用域!因此,class 体内的本地方法作用域在名称查找期间无法访问 class 作用域。 (至少没有资格)。换句话说,class 的范围只能从它自己的 class 定义的顶级代码体中访问。如果不直接使用点符号,则无法从其他任何地方访问它,无论是在内部还是外部