看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在查找链中的优先级要比实例的原生属性高