工作中遇到一个问题,就是有一些需要对数据库做全表扫描,而且对结果要求比较宽松的地方,总觉得可以找地方优化,比如暂时保存计算结果。

首先想起来的就是functools.lru_cache,但是可惜在python2.7中没有这个装饰器。

然后就是在stackoverflow找了一个:

(来源:https://stackoverflow.com/questions/11815873/memoization-library-for-python-2-7)

 import time
import functools
import collections def lru_cache(maxsize = 255, timeout = None):
"""lru_cache(maxsize = 255, timeout = None) --> returns a decorator which returns an instance (a descriptor). Purpose - This decorator factory will wrap a function / instance method and will supply a caching mechanism to the function.
For every given input params it will store the result in a queue of maxsize size, and will return a cached ret_val
if the same parameters are passed. Params - maxsize - int, the cache size limit, anything added above that will delete the first values enterred (FIFO).
This size is per instance, thus 1000 instances with maxsize of 255, will contain at max 255K elements.
- timeout - int / float / None, every n seconds the cache is deleted, regardless of usage. If None - cache will never be refreshed. Notes - If an instance method is wrapped, each instance will have it's own cache and it's own timeout.
- The wrapped function will have a cache_clear variable inserted into it and may be called to clear it's specific cache.
- The wrapped function will maintain the original function's docstring and name (wraps)
- The type of the wrapped function will no longer be that of a function but either an instance of _LRU_Cache_class or a functool.partial type. On Error - No error handling is done, in case an exception is raised - it will permeate up.
""" class _LRU_Cache_class(object):
def __init__(self, input_func, max_size, timeout):
self._input_func = input_func
self._max_size = max_size
self._timeout = timeout # This will store the cache for this function, format - {caller1 : [OrderedDict1, last_refresh_time1], caller2 : [OrderedDict2, last_refresh_time2]}.
# In case of an instance method - the caller is the instance, in case called from a regular function - the caller is None.
self._caches_dict = {} def cache_clear(self, caller = None):
# Remove the cache for the caller, only if exists:
if caller in self._caches_dict:
del self._caches_dict[caller]
self._caches_dict[caller] = [collections.OrderedDict(), time.time()] def __get__(self, obj, objtype):
""" Called for instance methods """
return_func = functools.partial(self._cache_wrapper, obj)
return_func.cache_clear = functools.partial(self.cache_clear, obj)
# Return the wrapped function and wraps it to maintain the docstring and the name of the original function:
return functools.wraps(self._input_func)(return_func) def __call__(self, *args, **kwargs):
""" Called for regular functions """
return self._cache_wrapper(None, *args, **kwargs)
# Set the cache_clear function in the __call__ operator:
__call__.cache_clear = cache_clear def _cache_wrapper(self, caller, *args, **kwargs):
# Create a unique key including the types (in order to differentiate between 1 and '1'):
kwargs_key = "".join(map(lambda x : str(x) + str(type(kwargs[x])) + str(kwargs[x]), sorted(kwargs)))
key = "".join(map(lambda x : str(type(x)) + str(x) , args)) + kwargs_key # Check if caller exists, if not create one:
if caller not in self._caches_dict:
self._caches_dict[caller] = [collections.OrderedDict(), time.time()]
else:
# Validate in case the refresh time has passed:
if self._timeout != None:
if time.time() - self._caches_dict[caller][1] > self._timeout:
self.cache_clear(caller) # Check if the key exists, if so - return it:
cur_caller_cache_dict = self._caches_dict[caller][0]
if key in cur_caller_cache_dict:
return cur_caller_cache_dict[key] # Validate we didn't exceed the max_size:
if len(cur_caller_cache_dict) >= self._max_size:
# Delete the first item in the dict:
cur_caller_cache_dict.popitem(False) # Call the function and store the data in the cache (call it with the caller in case it's an instance function - Ternary condition):
cur_caller_cache_dict[key] = self._input_func(caller, *args, **kwargs) if caller != None else self._input_func(*args, **kwargs)
return cur_caller_cache_dict[key] # Return the decorator wrapping the class (also wraps the instance to maintain the docstring and the name of the original function):
return (lambda input_func : functools.wraps(input_func)(_LRU_Cache_class(input_func, maxsize, timeout)))

但是会出现一个问题,那就是以后部署的话,会有多个服务器部署在nginx后面,但是这些缓存结果是保存在单个服务器的,那么就会在不同的请求结果就可能出现不一致,那么怎么办?

放进redis?

然后就想起来了flask-cache,但是可惜,如果用这个缓存普通函数的计算结果会报错。

最后,只能自己动手写一个了:

 def cache_func_redis(timeout=100):
def decorator(func):
@wraps(func)
def wrapper(*args,**kwargs):
lst_dct = sorted([{k: kwargs[k]} for k in kwargs], key=lambda d:d.keys()[0])
lst = [str(d.values()[0]) for d in lst_dct]
k = ''.join([func.__name__, str(args), ''.join(lst)])
r = redis.Redis(connection_pool=cache_redis)
d = r.get(k)
if d:
res = json.loads(d)['res']
return res
res = func(*args, **kwargs)
d = json.dumps({
'res': res
})
r.set(k, d)
r.expire(k, timeout)
return res
return wrapper
return decorator

利用函数名和传入的参数,提取特征值作为redis中存入的名字,把计算结果存入redis,失效时间为timeout,但是需要注意的是,

*如果传入的参数为字典,那么可能不会被命中

*被缓存的结果必须为对准确性时效性要求不高的地方

*被缓存的结果应该为基本的python数据结构,否则可能会报错

*还没有做压力测试,等做了压力测试把结果传上来

参考资料:

https://github.com/python/cpython/blob/3.4/Lib/functools.py

https://stackoverflow.com/questions/11815873/memoization-library-for-python-2-7

[python]缓存函数结果进redis的更多相关文章

  1. 缓存系列之四:redis持久化与redis主从复制

    一:redis 虽然是一个内存级别的缓存程序,即redis 是使用内存进行数据的缓存的,但是其可以将内存的数据按照一定的策略保存到硬盘上,从而实现数据持久保存的目的,redis支持两种不同方式的数据持 ...

  2. python第六天 函数 python标准库实例大全

    今天学习第一模块的最后一课课程--函数: python的第一个函数: 1 def func1(): 2 print('第一个函数') 3 return 0 4 func1() 1 同时返回多种类型时, ...

  3. 阿里云Centos7.6上面部署基于redis的分布式爬虫scrapy-redis将任务队列push进redis

    Scrapy是一个比较好用的Python爬虫框架,你只需要编写几个组件就可以实现网页数据的爬取.但是当我们要爬取的页面非常多的时候,单个服务器的处理能力就不能满足我们的需求了(无论是处理速度还是网络请 ...

  4. Python之函数进阶

    本节内容 上一篇中介绍了Python中函数的定义.函数的调用.函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性: 递归函数 嵌套函数与闭包 匿名函数 高阶函数 内置函数 总结 一.递归函 ...

  5. python基础—函数装饰器

    python基础-函数装饰器 1.什么是装饰器 装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能. 装饰器的返回值是也是一个函数对象. 装饰器经常用于有切 ...

  6. 【转】Python之函数进阶

    [转]Python之函数进阶 本节内容 上一篇中介绍了Python中函数的定义.函数的调用.函数的参数以及变量的作用域等内容,现在来说下函数的一些高级特性: 递归函数 嵌套函数与闭包 匿名函数 高阶函 ...

  7. Python 全栈开发四 python基础 函数

    一.函数的基本语法和特性 函数的定义 函数一词来源于数学,但编程中的「函数」概念,与数学中的函数是有很大不同的.函数是指将一组语句的集合通过一个名字(函数名)封装起来,要想执行这个函数,只需调用其函数 ...

  8. Python int() 函数

    Python int() 函数  Python 内置函数 描述 int() 函数用于将一个字符串或数字转换为整型. 语法 以下是 int() 方法的语法: class int(x, base=10) ...

  9. Python oct() 函数

    Python oct() 函数  Python 内置函数 描述 oct() 函数将一个整数转换成8进制字符串. 语法 oct 语法: oct(x) 参数说明: x -- 整数. 返回值 返回8进制字符 ...

随机推荐

  1. Visual Leak Detector原理剖析

    认识VLD VLD(Visual Leak Detector)是一款用于Visual C++的开源内存泄漏检测工具,我们只需要在被检测内存泄漏的工程代码里#include “vld.h”就可以开启内存 ...

  2. backup服务器之rsync服务

    backup服务器之rsync服务   rsync是开源的.快速的.多功能的可实现全量及增量的本地或远程数据同步备份的优秀工具.它拥有scp.cp的全量复制功能,同时比scp.cp命令更优秀.更强大. ...

  3. flask配置日志输出文件

    1.flask可以通过日志库来指点日志输出的路径, 配置日志输出的连接:www.gaodin.com

  4. VC++的全局变量(转)

    全局变量一般这样定义:1.在一类的.cpp中定义 int myInt;然后再在要用到的地方的.cpp里extern int myInt:这样就可以用了. 2.在stdafx.cpp中加入:int my ...

  5. 【codeforces】【比赛题解】#868 CF Round #438 (Div.1+Div.2)

    这次是Div.1+Div.2,所以有7题. 因为时间较早,而且正好赶上训练,所以机房开黑做. 然而我们都只做了3题.:(. 链接. [A]声控解锁 题意: Arkady的宠物狗Mu-mu有一只手机.它 ...

  6. go语言项目汇总

    Horst Rutter edited this page 7 days ago · 529 revisions Indexes and search engines These sites prov ...

  7. Scrapy命令行工具简介

    Windows 10家庭中文版,Python 3.6.4,virtualenv 16.0.0,Scrapy 1.5.0, 在最初使用Scrapy时,使用编辑器或IDE手动编写模块来创建爬虫(Spide ...

  8. python网络编程--线程join和Daemon(守护进程)

    一:什么情况下使用join join([timeout])调用join函数会使得主调线程阻塞,直到被调用线程运行结束或超时. 参数timeout是一个数值类型,用来表示超时时间,如果未提供该参数,那么 ...

  9. maven网址

    http://www.yiibai.com/maven/maven_environment_setup.html

  10. opencv(2)绘图

    绘制直线 函数为:cv2.line(img,Point pt1,Point pt2,color,thickness=1,line_type=8 shift=0) 有值的代表有默认值,不用给也行.可以看 ...