冒充优雅地实现准时缓存装饰器
PBFT算法java实现
参考资料
- Python 工匠:运用装潢器的技能
- 一日一技:完成有逾期时刻的LRU缓存
此次的参考资料写在前面,由于写得真不错!入手下手浏览本篇分享前,发起先浏览参考资料,假如还不能完成定时缓存装潢器,再继承从这里入手下手读。
完成思绪
功用拆分:
- 缓存上次函数运转的结果一段时刻。
- 把它封装成装潢器。
定时缓存
尽人皆知,python的functools
库中有lru_cache
用于构建缓存,而函数参数就是缓存的key
,因而,只要把缓存空间设置为1
,用时刻值作为key
,即可完成定时实行函数。细节就去看参考资料2吧,我这里就不赘述了。
详细完成以下:
""" 定时实行delay_cache """
import time
from functools import lru_cache
def test_func():
print('running test_func')
return time.time()
@lru_cache(maxsize=1)
def delay_cache(_):
return test_func()
if __name__ == "__main__":
for _ in range(10):
print(delay_cache(time.time()//1)) # 1s
time.sleep(0.2)
程序输出:
running test_func
1582128027.6396878
1582128027.6396878
running test_func
1582128028.0404685
1582128028.0404685
1582128028.0404685
1582128028.0404685
1582128028.0404685
running test_func
1582128029.0425367
1582128029.0425367
1582128029.0425367
能够看到,test_func
在距上次挪用1s
内直接输出缓存结果,挪用距离凌驾1s
时test_func
才会被真正实行。
手动完成缓存须要用字典,这里用lru_cache
装潢器替代了庞杂的字典完成,就很文雅;-)
装潢器
装潢器的作用呢,就是给函数戴顶帽子,然后函数该干吗干吗去,然则他人瞥见的已不是本来的函数,而是戴帽子的函数了。哈哈。
@delay_cache(time.time()//1) # (midori)帽子
def test_func():
print('running test_func')
return time.time()
一个毛病的树模
完成这个delay_cache
:
...
import wrapt
...
def delay_cache(t):
@wrapt.decorator
def wrapper(func, isinstance, args, kwargs):
# 给func加缓存
@lru_cache(maxsize=1)
def lru_wrapper(t):
return func()
return lru_wrapper(t)
return wrapper
...
运转这段程序,就会获得毛病的结果……(嘿嘿)
test 1582129926.0
running test_func
1582129926.4459314
test 1582129926.0
running test_func
1582129926.6466658
test 1582129926.0
...
能够看到,定时缓存彷佛消逝了一样。原因是装潢器返回的是wrapper
函数,而参数t
被wrapper
函数消除在外了。用print
打印t
,就会发明t
一向没有变。
等等,假如t
稳定,那不应该是一向取缓存结果吗?
- 现实老是严酷的,
wrapper
函数返回的是lru_wrapper(t)
,是一个结果,而不是lru_wrapper
函数,因而不幸的lru_cache
随着实行完的lru_wrapper
,被扔进了垃圾桶,今后被永久忘记。比及下一次实行到这里,只管新的t
雷同,然则lru_cache
也是新的,它基础不记得本身曾与t
还有过一段优美的姻缘过往……
证据呢?假如你也和我一样八卦的话,就去搞个全局变量,在lru_wrapper
初次运转的时刻把它存下来,背面的挪用就端赖这个全局变量,然后输出结果就稳定了。(要记得只须要在lru_wrapper
初次运转的时刻把函数赋值给全局变量!) - 现实老是严酷的×2,就算证明了
lru_cache
和t
隔世的姻缘,我们的需求也不会完成,由于之前说过,参数t
被wrapper
函数消除在外了。
假如不把t
作为装潢器的参数,而作为被装潢函数的参数呢?功用却是完成了,但是装潢器失去了它的代价,而且每一个用户函数,比方这里的test_func
,都要加上时刻盘算,变成test_func(time.time()//1, ...):
,到时刻time
模块满天飞,难以直视,惨绝人寰。
正解
用类来做装潢器,类实例化今后就能够一向相伴lru_cache
摆布,为它保驾护航。有关类装潢器的内容看参考资料1
class DelayCache(object):
def __init__(self, delay_s):
self.delay_s = delay_s
@wrapt.decorator
def __call__(self, func, isinstance, args, kwargs):
self.func = func
self.args, self.kwargs = args, kwargs
hashable_arg = pickle.dumps((time.time()//self.delay_s, args, kwargs))
return self.delay_cache(hashable_arg)
@lru_cache(maxsize=1)
def delay_cache(self, _):
return self.func(*self.args, **self.kwargs)
新的帽子做好了,给函数戴上碰运气:
...
@DelayCache(1) # 缓存 1s
def test_func(_):
print('running test_func')
return time.time()
测试下结果:
if __name__ == "__main__":
for _ in range(10):
print(test_func(1)) # 只取定时缓存
time.sleep(0.2)
# 测试结果:
# running test_func # 初次运转定时不是设定的1s,下面给出处理方案
# 1582132259.4029999
# 1582132259.4029999
# 1582132259.4029999
# running test_func
# 1582132260.0045283
# 1582132260.0045283
# 1582132260.0045283
# 1582132260.0045283
# 1582132260.0045283
# running test_func
# 1582132261.0072334
# 1582132261.0072334
if __name__ == "__main__":
for i in range(10):
print(test_func(i)) # 每次都实行函数
time.sleep(0.2)
# 测试结果:
# running test_func
# 1582132434.0865102
# running test_func
# 1582132434.2869732
# running test_func
# 1582132434.4875488
# ...
哈哈,这下终究搞定了。不过又冒出来2个问题:
- 初次运转的定时价并非
1s
。
函数每次入手下手计时的时刻点都是随机的,而缓存更新却依托秒进位,所以初次运转的缓存时刻多是0~1s
内恣意一个时刻点到1s
,所以不准。要处理这个问题,就要让时刻从0
入手下手计时。我的做法是用一个self.start_time
属性纪录函数初次运转的时刻,然后盘算现实距离的时刻,用取到的时刻减去这个纪录值,如许肇端时刻就肯定从0
入手下手了。 - 参数转变的时刻计时没有复位。
须要复位的处所就是实行delay_cache
的处所,所以在delay_cache
函数里复位计时价即可。
别的,每次复位后,(time.time() - self.start_time)
都从新从0
入手下手累加,(time.time() - self.start_time) // self.delay_s
的输出会变成...0,1,0,0,0,0,1,0,0,0,0,1,0,0...
,如许就不能作为lru_cache
的key
来剖断了,所以增加一个self.tick
属性,把状况锁住,变成...0,0,1,1,1,1,1,0,0,0,0,0,1,1...
。
修改的处所直接看终究代码吧。
终究代码
import time
import pickle
import wrapt
from functools import lru_cache
class DelayCache(object):
def __init__(self, delay_s):
self.delay_s = delay_s
self.start_time = 0
self.tick = 0
@wrapt.decorator
def __call__(self, func, instance, args, kwargs):
self.func = func
self.args, self.kwargs = args, kwargs
if time.time() - self.start_time > self.delay_s:
self.tick ^= 1 # 状况切换,相当于自锁开关
hashable_arg = pickle.dumps((self.tick, args, kwargs))
return self.delay_cache(hashable_arg)
@lru_cache(maxsize=1)
def delay_cache(self, _):
self.start_time = time.time() # 计时复位
return self.func(*self.args, **self.kwargs)
@DelayCache(delay_s=1) # 缓存1秒
def test_func(arg):
print('running test_func')
return arg
if __name__ == "__main__":
for i in [1, 1, 2, 3, 1, 1, 1, 1, 1, 1, 1, 1]:
print(test_func(i))
time.sleep(0.4)
用@wrapt.decorator
抵抗套娃,用@lru_cache
干掉字典,代码变得非常清新啊……
测试结果
running test_func
1
1
running test_func
2
running test_func
3
running test_func
1
1
1
running test_func
1
1
1
running test_func
1
1
假如有什么看法和补充,或许有更文雅的完成体式格局,迎接在批评区留言~
ASP.NET Core 配置和使用环境变量