python描述符应用
个人认为python中描述符协议是一个比较神奇的存在,是因为如果你不去了解property的内部实现,没有深究别人写的__set__、__get__方法。或许写python很多年都对这个东西没什么了解。然而这玩意儿有时候真的挺好用的。本文不会详述描述符协议。会着重讲一个小例子。如果以前没有接触过描述符,请依次查看文末相关资料的两篇文章
适用范围
描述符协议都是针对对象属性的访问。先要明白我们不会去针对一个全局的def使用property进行装饰。我们一般都是在类里面使用。可以对类的访问使用描述符(比较少用),更常用的是针对类实例的访问使用描述符协议
资料描述符和非资料描述符的区别
1 | class RevealAccess(object): |
首先,当对属性x进行访问的时候不是直接返回描述符对象,而是按照描述符规则执行了描述符对象的__get__等方法!资料描述符就是同时实现了__get__和__set__,区别就是是资料描述符的时候就按照资料描述符的__get__、__set__来。非资料描述符的时候那就先访问instance.__dict__['x']
,没有就在按照非资料描述符的__get__来。上面的例子先注释掉__set__就是非资料描述符,对实例属性进行访问的时候先访问了instance.__dict__没有就使用了描述符对象的__get__方法。当为资料描述符的时候纵然对instance.__dict__设置了。依然会调用描述符对象。
示例应用:property加强版,增加缓存(最简代码)
首先来一个例子
1 | class Foo(object): |
该方法使用python函数的默认参数只初始化一次对结果进行缓存。缺点比较明显。1.无法复用。2.对原函数进行了修改
下面看pyramid的实现
1 | class reify(object): |
使用的是非资料描述符,第一次对属性进行访问的时候,因为f.__dict__是没有jammy的。故而访问了描述符,在描述符__get__里面将结果加入到了f.__dict__里面。后面访问就没__get__什么事儿了。实现了对结果的缓存
再看werkzeug的实现
1 | class cached_property(property): |
可以看到和资料描述符的基本一样。可以看到__set__基本没什么用,仅仅只是表面了这是一个资料描述符。而且同样的,为了方便也一样把结果存储到了f.__dict__里面
_getattr_、__getattribute\
虽然都有get,可是区别是很大的。
描述符是控制对象某个属性的访问
(所以看到描述符对象一般主要用__get__
,__get__
)。__getattr__
它控制属性不存在的时候该咋办
__getattribute__
和上面相反,默认行为,从__dict__中找到属性值返回
1 | class Foo(object): |
再加一个类似的例子吧O_o(https://github.com/faif/python-patterns/blob/master/lazy_evaluation.py)
相关资料
python官方文档描述符指南(译)
python描述符解密
Difference between _getattr_ vs _getattribute_