引言
以前对这一部分的理解比较混乱,查找了一些资料,自我整理一下。
作用域
对于变量的搜索是按照一定顺序进行的,同名变量将会存在互相屏蔽的问题,所以需要弄清楚python里的作用域信息。在python中作用域一共有四个,按照顺序被搜索:
1. (Local)局部作用域,每当调用一个函数的时候就创建了一个局部作用域,它最先被搜索。
2. (Enclosing)嵌套的父级函数的局部作用域
3. (global)全局作用域
4. (built-in)内建作用域,这个是内建函数和类的作用域。
变量的搜索是由内而外一层层进行的,所以内层的变量会屏蔽外层的变量。但是可以通过使用global和nonlocal关键字,显式的声明一个全局变量或者非局部变量。
对象属性查找顺序
- 正常情况下,python中类的属性保存在实例的
__dict__
属性中,其中__dict__
是一个字典,foo.bar等价于
foo.__dict__['bar']
同时对于实例的属性的赋值,例如foo.bar = 'yes'
等价于foo.__dict__['bar'] = 'yes'
。 - 但是在访问时不会直接进行访问,会调用一个叫做
__getattribute__
的函数,对于属性进行获取;通过__setattribute__
对于属性进行修改;但是这些都仅限于__dict__
,如果找不到对应属性,则抛出AttributeError
。 - 这个时候就会接着调用一个叫做
__getattr__
的函数(如果你定义了这个函数),如果这样都找不到就会真的抛出
AttributeError
,对于__setattr__
也是类似的。 - 而我们通常使用的两个内置函数
getattr()和setattr()
实际上也是通过这样一个调用过程获取对象的属性。 - 而python 对象还有一个跟
__getattribute__,__getattr__
十分类似的一个属性__get__
,但是它们的作用却不太相同,
所有定义了__get__
属性的类都会被转换为一个描述器,具体描述器这里就不展开了,但是有一点就是,在获取对象属性时,
如果属性有__get__
方法,那么就会自动展开__get__
方法。
继承属性查找顺序(MRO)
MRO的全称是Method Resolution Order,即方法解析顺序,它用来定义类继承链中如何对类属性和方法进行查找,保证属性查找不会
出现冲突。现在实际采用的是C3算法,下面简单的描述一下:
我们把类C的线性化(MRO)记为L[C]=[C1, C2,…,CN]。其中C称为L[C]的头,其余元素[C2,…,CN]称为尾。如果一个类C继承自基类
B1、B2、……、BN,那么可以根据以下两步计算出L[C]:
1. L[object] = [object]
2. L[C(B1…BN)] = [C] + merge(L[B1]…L[BN], [B1]…[BN])
而其中merge函数定义如下:
1. 检查第一个列表的头元素(如 L[B1] 的头),记作 H。
2. 若 H 未出现在其它列表的尾部,则将其输出,并将其从所有列表中删除,然后回到步骤1;否则,取出下一个列表的头部
记作 H,继续该步骤。
3. 重复上述步骤,直至列表为空或者不能再找出可以输出的元素。如果是前一种情况,则算法结束;如果是后一种情况,说
明无 法构建继承关系,Python 会抛出异常。
详细的例子可以看这里 [C3算法](https://en.wikipedia.org/wiki/C3_linearization)
在实际使用中,可以通过类的`__mro__`属性和`mro`方法获得类的属性查找顺序,返回结果类似(C, A, B, object),
而与此相关有一个十分常用的函数super,其形式为super(type, [object or type]),实际上super函数不能简单的看作获得父
类对象,实际上它是在mro的基础列表的基础上获得下一个基类对象。第二个参数提供mro列表,第一个参数定位当前位置,
并返回列表中的下一个类对象,类似于下面的实现:
def super(cls, inst):
mro = inst.__class__.mro()
return mro[mro.index(cls)+1]