cached_property的理解与使用

本文详细解析了cached_property装饰器的工作原理及其实现方式。通过对比Flask和sea中的实现,介绍了如何利用该装饰器缓存属性的计算结果,避免重复计算,提高程序效率。

今天在同事的推荐下,看了下扇贝的sea代码,
没细看,突然看到了cached_property的代码,这个让我突然想到了我们内部爬虫框架的cached_property,当时我在写这部分代码的时候主要目的有点类似如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
class A(object):
def __init__(self):
pass

def random(self):
return random.random()


class B(object):

@cached_property
def a(self):
return A()

A为一个类,然后B有点类似A的一个超集,平常使用的使用为实例化B, 然后为了操作A下面的方法,基本流程为:

1
2
3
>>> b = B()
>>> b.a.random()
>>> b.a.other_method()

当时设计的时候,并没有想到通过property这个将方法变成属性的方法,当时想我只需要初始化两次就行,如下:

1
2
3
4
5

>>> b = B()
>>> a = b.a()
>>> a.random()
>>> a.other_method()

但是后来我就意识到我这种设计有点low,因为其他同事调用的时候可能不会这么使用,另外因为B下面还会有其他类似A这种东西,例如:

1
2
3
4
5
6
7
8
9
class B(object):

@cached_property
def a(self):
return A()

@cached_property
def c(self):
return C()

难道每次使用的时候都拿到c这个实例吗,有点傻,所以参考了Flaskcached_property实现,改成了如上了流程.那么进入主题,cached_property到底干了什么事情?

先看Flask的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


class _Missing(object):

def __repr__(self):
return 'no value'

def __reduce__(self):
return '_missing'


_missing = _Missing()


class cached_property(property):
def __init__(self, func, name=None, doc=None):
self.__name__ = name or func.__name__
self.__module__ = func.__module__
self.__doc__ = doc or func.__doc__
self.func = func

def __set__(self, obj, value):
obj.__dict__[self.__name__] = value

def __get__(self, obj, type=None):
if obj is None:
return self
value = obj.__dict__.get(self.__name__, _missing)
if value is _missing:
value = self.func(obj)
obj.__dict__[self.__name__] = value
return value

再看sea的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

class cached_property:
""" thread safe cached property """

def __init__(self, func, name=None):
self.func = func
self.__doc__ = getattr(func, '__doc__')
self.name = name or func.__name__
self.lock = Lock()

def __get__(self, instance, cls=None):
with self.lock:
if instance is None:
return self
try:
return instance.__dict__[self.name]
except KeyError:
res = instance.__dict__[self.name] = self.func(instance)
return res

唯一不同点应该就在于加了一个锁吧,那么抛去锁的部分,单纯讲cached_propery的实现

1
2
3
4
5

class B(object):
@cached_property
def b(self):
pass

第一步

学过Python的应该蛮清楚关于装饰器这个概念,当将cached_property加在b上时,就已经完成了cached_property类的实例化(看最后一个类版本的计算时间装饰器),那怎么传进去的呢?

1
2
3
4
5
6
7
8
9


class cached_property:
""" thread safe cached property """

def __init__(self, func, name=None):
self.func = func # 这个func对应上面的就为b
self.__doc__ = getattr(func, '__doc__') # None
self.name = name or func.__name__ # func.__name__ 为b

第二步

调用的时候怎么个过程?看Python Document

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Property(object):
"Emulate PyProperty_Type() in Objects/descrobject.c"

def __init__(self, fget=None, fset=None, fdel=None, doc=None):
self.fget = fget
self.fset = fset
self.fdel = fdel
if doc is None and fget is not None:
doc = fget.__doc__
self.__doc__ = doc

def __get__(self, obj, objtype=None):
if obj is None:
return self
if self.fget is None:
raise AttributeError("unreadable attribute")
return self.fget(obj)

def __set__(self, obj, value):
if self.fset is None:
raise AttributeError("can't set attribute")
self.fset(obj, value)

def __delete__(self, obj):
if self.fdel is None:
raise AttributeError("can't delete attribute")
self.fdel(obj)

def getter(self, fget):
return type(self)(fget, self.fset, self.fdel, self.__doc__)

def setter(self, fset):
return type(self)(self.fget, fset, self.fdel, self.__doc__)

def deleter(self, fdel):
return type(self)(self.fget, self.fset, fdel, self.__doc__)
1
2
3
4
5
6
7
8
9
10
11
def __get__(self, instance, cls=None):
# instance代表实例化B
with self.lock:
if instance is None:
return self
try:
return instance.__dict__[self.name]
except KeyError:
# 如果没有在B的__dict__找到self.name(即方法b), 那么self.func(即方法b)则会执行b(B), 并将其保存到实例化b的__dict__下
res = instance.__dict__[self.name] = self.func(instance)
return res

总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class B(object):

@cached_property
def a(self):
return A()

@cached_property
def c(self):
return C()


>>> b = B()
>>> for i in range(3):
print(b.__dict__)
b.a.random()

剩下自行理解吧..

1
2
3
4
5
6
7
8
9
10
11
# 类版本的计算时间的装饰器
class cal_time(object):
def __init__(self, func):
self.func = func

def __call__(self, *args, **kwargs):
s = time.time()
resp = self.func(*args, **kwargs)
e = time.time()
print("使用时长:{}".format(e - s))
return resp
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ox180x

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值