python descriptor的妙用

看bottle的源码时发现的。

import functools
import random

class cache(object):

    def __init__(self, func):
        functools.update_wrapper(self, func, updated=[])
        self.getter = func

    def __get__(self, obj, cls):
        value = self.getter(cls)
        setattr(cls, self.__name__, value)
        return value


class A(object):
    @cache
    def m(cls):
        return "this is a methods %d" % random.randint(1,9)

a = A()
print a.m



cache对象用__call__方法,所以可以作为一个decorator。将类A中方法m作为self对象的func属性。注意cache()返回产生的是一个对象,也就是被修改过的self,这个对象在类A中和名字m关联。也就是原来的方法m已经被这个对象替换掉了,当然m对象依然有一个指向原函数的引用。

然后产生一个类A的实例a,当第一次访问a的m属性的时候会调用m的__get__函数,也就是m是一个descriptor。在__get__函数的内部对A新建了一个和m同名的对象(同名的原因就是先开始用update_wrapper将方法m的__name__拷贝到了self的__name__),这个对象的值就是函数m的返回值。假设这里返回的是"this is a methods 3",之后的多次调用会发现函数返回的是一样的值。从而也就实现了方法变为对象的静态属性。

这里可以在__get__函数后定义__set__函数,这样m就变成一个data descriptor了,然后在用上面的方法会发现结果一样。因为这时候属性m还是在A.__dict__中。

如果把setattr(cls, self.__name__, value)换成setattr(object, self.__name__, value)。然后的每次引用m.a就会发现值不同,因为data descriptor在查找链中的优先级要比实例的原生属性高