1 摘要
介绍python属性查找的相关基础知识,通过底层代码的分析,详细描述了python中对象属性的查找/赋值过程以及在object和type中对描述器的不同处理。并结合这些知识,简单探寻了一下描述器在python实现中的作用。
2 准备
在详解python属性查找之前,我们先来了解一些相关的概念,以便于理解整个查找过程。
2.1 描述器
描述器(descriptor )是一个有 “绑定行为” 的对象属性(object attribute),它的访问控制被描述器协议方法重写。描述器协议的方法为:
descr.__get__(self, obj, type=None) --> value
descr.__set__(self, obj, value) --> None
descr.__delete__(self, obj) --> None
一个对象具有其中任一个方法就会成为描述器,从而在被当作对象属性时重写默认的查找行为。
如果一个对象同时定义了 __get__() 和 __set__() ,它叫做资料描述器(data descriptor)。仅定义了 __get__() 的描述器叫非资料描述器(non-data descriptor, 常用于方法,当然其他用途也是可以的)。
资料描述器和非资料描述器的区别在于:相对于实例的字典的优先级。 如果实例字典中有与描述器同名的属性,如果描述器是资料描述器,优先使用资料描述器,如果是非资料描述器,优先使用字典中的属性。后面内容会具体介绍。
描述器是强大的,应用广泛的。描述器正是属性, 实例方法, 静态方法, 类方法和 super 背后的实现机制(详细的解释请点击这里)。描述器在 Python 自身中广泛使用,用以实现 Python2.2 中引入的新式类。描述器简化了底层的C代码,并为 Python 的日常编程提供了一套灵活的新工具。
2.2 Python 方法解析顺序
在支持多重继承的编程语言中,查找方法具体来自那个类时的基类搜索顺序通常被称为方法解析顺序(Method Resolution Order),简称 MRO。Python中查找属性也遵循 MRO。对于仅支持单重继承的语言,MRO非常简单,只需要按着继承链搜索就行。但是在支持多继承的语言中,情况就比较复杂。“菱形继承” 的问题在 python 引入 “新式类” 后更加突出。
Python的历史版本中先后出现三种不同的MRO:经典方式、Python2.2 新式算法、Python2.3 新式算法(也称作C3)。Python 3中只保留了最后一种,即C3算法。C3算法最早被提出是用于Lisp的,在 python 中采用主要是为了解决之前之前基于深度优先搜索算法不满足本地优先级和单调性的问题:
- 本地优先级: 指声明时父类的顺序,比如C(A,B),在访问C类对象属性时,根据声明顺序应该先查找A类再查找B类。
- 单调性: 指如果在解析C类继承顺序时,A类排在B类的前面,那么在C类所有的子类里也必须满足这个顺序。
从 Python2.2 开始为解决引入新类所带来的方法解析顺序,采用的方案是在类定义时就计算出它的MRO ,并存储为该类对象的一个属性 __mro__ 。然后在查找属性时按照 __mro__ ** 依次搜索基类。
在大多数编程语言中,类就是一组用来描述如何生成一个对象的代码段。Python中的类还远不止如此。在python中类同样也是一种对象,只要你使用关键字class,解释器在执行的时候就会创建一个对象。例如:
1
| class ObjectCreator(object):pass
|
上面的代码会在python解释器执行的时候生成一个名叫 ObjectCreator 的对象。这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。 创建 “类” 的 “类” 就是元类(metaclass),python默认的内建元类是type。
1 2 3 4
| >>> object.__class__ <type 'type'> >>> type(object) <type 'type'>
|
在 python 中通过 type(obj) ,或者 obj.__class__ 来获取对象的类型(type)。这里有个特别的 type 的 type 是 type。
1 2 3 4 5 6 7 8
| >>> type(object) <type 'type'> >>> type(type) <type 'type'> >>> type.__class__ <type 'type'> >>> object.__class__ <type 'type'>
|
换句话说,元类(metaclass)的实例化是类(class),类(class)的实例化是类实例对象(object)。
注:type 这个类型(内建函数)根据传入的参数不同具有两个不同的功能,这可能是出于向后兼容的原因。
- type(object) -> the object’s type,返回的是 object 实例的类型。
- type(name, bases, dict) -> a new type,返回的是一个新类型。