python描述符相关和属性查找策略
描述符(descriptor)
描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们通过查看实例和类的字典即可知晓。
只要类重写任何下面的一个方法,类就被看作是descriptor
,当这些descriptor在另外一个类中作为属性被访问时, 就可以不去采用默认的查找属性的顺序。
-
1、
__get__(self, instance, owner)
获取属性时调用,返回设置的属性值,通常是set中的value,或者附加的其他组合值。 -
2、
__set__(self, instance, value)
设置属性时调用,返回None. -
3、
__delete__(self, instance)
其中,instance是这个描述符属性所在的类的实体,而owner是描述符所在的类。
两种种类的描述符
数据描述符(data descriptor)和非数据描述符(non-data descriptors)
数据描述符:定义了set 和get方法的对象
非数据描述符:只定义了get方法的对象。
非数据描述符在python中
通常方法都是非数据描述符。比如后面会谈到的staticmethod,classmethod等。
此利用描述符的原理,我们完全可以自定义模拟@classmethod、@staticmethd、@property、等属性。实现这种类似系统的属性,我们还需要装饰器作为修饰 。
例如:(使用装饰器@的结果等价于将函数变为属性:)
声明非数据描述符descriptor_classmethod
@descriptor_classmethod,结合装饰器做成一个系统描述符。
参考
属性查找策略:
在Python中,属性查找(attribute lookup)是比较复杂的,特别是涉及到描述符descriptor的时候。
大致优先级如下:
类
__dict__
属性 > 数据描述符 > 实例__dict__
属性 > 非数据描述符 > 找不到的属性触发getattr()
通过实例。
访问属性时,__getattribute__
内部的搜索逻辑:
注意: 数据描述符永远是针对类来说的, 所以实例访问触发的__getattribute__
第一步不会搜索实例obj的__dict__
中attr是否data descriptor, 而是去搜索其类或其父/超类。
- (1)如果“attr”是出现在类或其父/超类的
__dict__
中,检测attr是否data descriptor, 是则调用其get方法。 - (2)如果“attr”出现在obj的
__dict__
中, 那么直接返回 obj.dict['attr'] - (3)如果“attr”出现在类或其父/超类的
__dict__
中(这一步时attr已经不是data descriptor)
(3.1)如果attr是non-data descriptor,那么调用其get方法, 否则
(3.2)返回 dict['attr'] - (4)如果类有getattr方法,调用getattr方法,否则
- (5)抛出AttributeError
通过类名。
访问属性时的搜索逻辑(不知道此时会不会调用getAttribute):
- (1)如果“attr”是出现在类或其父/超类的
__dict__
中,优先返回“attr” - (2)如果“attr”是出现在类或其父/超类的
__dict__
中,是data descriptor,则调用其get方法。