定义
一个类中如果定义了__get__()
, __set__()
,__delete__()
三种方法则被称为描述器。仅定义了__get__()
方法的称为非资料描述器, 定义了__get__()
, __set__()
方法的称为资料描述器。
描述器的主要用处是拦截某个类中的属性调用。
用处
从@property说起
要在类中定义一个调用时动态变化的属性可以使用 @property 装饰器。比如定义一个鸡蛋类:
1 |
|
输出:1
2
3
4
当前鸡蛋价格:10 人民币,1.4705882352941178 美元,11.494252873563218 港元
当前鸡蛋价格:13.6 人民币,2.0 美元,15.632183908045977 港元
当前鸡蛋价格:13.05 人民币,1.9191176470588236 美元,15.000000000000002 港元
可以发现美元和港元价格的代码逻辑基本相同,如果需要更多货币价格则会出现更多重复代码。
用描述器实现
这时候就轮到描述器上场了。首先定义一个实现了描述器协议的价格类:
1 | class Price(object): |
接着用描述器重构上面的Egg类:
1 | class Egg(object): |
实例化描述器类即可,具体的代码逻辑都在描述器里定义好了。这里可以简单地将描述器理解成把 @property 的三个被装饰的方法抽象出来作为描述器类的类方法,以方便复用。
用描述器实现的Python语法
@property
@property本身也是通过描述器实现的。其 Python 等价代码如下:
1 | class Property(object): |
getattribute/get/getattr的区别
__getattribute__
是新式类的实例取属性时直接调用的方法。这个方法也定义了之后的调用顺序。
__get__
是上文说的描述器协议。
__getattr__
是其他地方都找不到这个属性时最后调用的方法。
具体调用关系是:首先调用__getattribute__
,__getattribute__
中定义了之后的调用顺序。默认的__getattribute__
中定义的查找顺序是:实例字典 –> 资料描述器 –> 类字典 –> 非资料描述器 –> __getattr__
。