在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor。descriptor通常用来改变默认的属性访问(attribute lookup), 前提是descriptor的实例是类的属性(class attribute)。下面的代码展示了简单的用法
1 # -*- coding: utf-8 -*- 2 class Des(object): 3 def __init__(self, init_value): 4 self.value = init_value 5 6 def __get__(self, instance, typ): 7 print(‘call __get__‘, instance, typ) 8 return self.value 9 10 def __set__(self, instance, value):11 print (‘call __set__‘, instance, value)12 self.value = value13 14 def __delete__(self, instance):15 print (‘call __delete__‘, instance)16 17 class Widget(object):18 t = Des(1)19 20 def main():21 w = Widget()22 print type(w.t)23 w.t = 124 print w.t, Widget.t25 del w.t26 print w.t27 28 29 if __name__==‘__main__‘:30 main()
这三个特殊的函数签名是这样的:
object.
__get__
(self, instance, owner):return value
object.
__set__
(self, instance, value):return None
object.
__delete__
(self, instance): return None
形参中,instance是类的实例(w), owner是类(widget)
w.t 等价于 Pro.__get__(t, w, Widget).而Widget.t 等价于 Pro.__get__(t, None, Widget)
descriptor主要用于控制属性的访问(读、写、删除)。python doc里面有写到,property()就是一个data descriptor实现(可参见这个文档)。 python2.2中,大量新式类的实现都基于descriptor
They are the mechanism behind properties, methods, static methods, class methods, and
super()
. They are used throughout Python itself to implement the new style classes introduced in version 2.2.
在实践中,我们有可能需要监控或者限制对属性的访问。比如,对象的一个属性被“莫名其妙”地修改了,但搜索所有文件有找不到可以的地方,那么我们可以通过__setattr__(self, k, v)方法,对于我们关心的 k 打印出调用栈。另外,也可以用property,示例代码如下:
1 class TestProperty(object): 2 def __init__(self): 3 self.__a = 1 4 5 @property 6 def a(self): 7 return self.__a 8 9 @a.setter10 def a(self, v):11 print(‘output call stack here‘)12 self.__a = v13 14 if __name__==‘__main__‘:15 t = TestProperty()16 print t.a17 t.a = 218 print t.a
如果需要禁止对属性赋值,或者对新的值做检查,也很容易修改上面的代码实现
既然有了property,那什么时候还需要descriptor呢?property最大的问题在于不能重复使用,即对每个属性都需要property装饰,代码重复冗余。而使用descriptor,把相同的逻辑封装到一个单独的类,使用起来方便多了。
笔者之前看bottle.py源码的时候,看到这么一个descriptor使用,部分源代码和测试代码如下:
1 import functools, time 2 class cached_property(object): 3 """ A property that is only computed once per instance and then replaces 4 itself with an ordinary attribute. Deleting the attribute resets the 5 property. """ 6 7 def __init__(self, func): 8 functools.update_wrapper(self, func) 9 self.func = func10 11 def __get__(self, obj, cls):12 if obj is None: return self13 value = obj.__dict__[self.func.__name__] = self.func(obj)14 return value15 16 class TestClz(object):17 @cached_property18 def complex_calc(self):19 print ‘very complex_calc‘20 return sum(range(100))21 22 if __name__==‘__main__‘:23 t = TestClz()24 print ‘>>> first call‘25 print t.complex_calc26 print ‘>>> second call‘27 print t.complex_calc
运行结果如下:
>>> first call
very complex_calc
4950
>>> second call
4950