方法和属性的有用缓存装饰器
easy-cache的Python项目详细描述
轻松缓存装饰器
这个包旨在简化基于python(主要是)web应用程序中的缓存和失效过程。可以缓存函数的执行结果;实例,类和静态方法;属性。缓存键可以以各种不同的方式构造,并且可以取决于任意数量的参数。
该包支持基于标记的缓存失效,并且可以更好地与django一起工作,但是可以使用任何其他框架–请参见下面的示例。
这个包的主要思想是:您不需要触摸任何现有的函数代码就可以缓存它的执行结果。
要求
库在以下环境中进行了测试:
- 巨蟒2.7、3.5、3.6
- django 1.8、1.11,>;=2.0.0
你可以随意试穿,但不一定能成功。如果您认为应该提交一个问题。
安装
pip install easy_cache
简介
缓存某些内容的不同方式
假设您有一个耗时的函数,并且您需要缓存执行结果,实现这一点的经典方法是下一个:
# classic wayfromdjango.core.cacheimportcachedeftime_consuming_operation(n):"""Calculate sum of number from 1 to provided n"""cache_key='time_consuming_operation_{}'.format(n)result=cache.get(cache_key,None)ifresultisNone:# not found in cacheresult=sum(range(n+1))# cache result for one hourcache.set(cache_key,result,3600)returnresultdefinvalidate_cache(n):cache.delete('time_consuming_operation_{}'.format(n))
好吧,我们必须添加烦人的样板代码来实现这一点。
现在让我们来看看easy_cache
如何避免这个问题并简化代码:
# easy wayfromeasy_cacheimportecached@ecached('time_consuming_operation_{n}',3600)deftime_consuming_operation(n):returnsum(range(n+1))definvalidate_cache(n):time_consuming_operation.invalidate_cache_by_key(n)
正如我们所看到的,函数代码没有问题。 包的核心是两个具有类似参数的装饰器:
ecached
应用于修饰任何可调用的缓存返回的结果。
参数:
缓存键
–缓存键生成器,默认值为无
,因此键将根据函数名、命名空间和传递的参数自动组合。还支持以下类型:- 字符串–可能包含python高级字符串格式语法,给定值将使用传递给修饰函数的参数的dict进行格式设置,请参见下面的示例。
- 字符串序列–每个字符串必须是函数参数名。
- 可调用–用于生成缓存键,修饰的函数参数将传递给此可调用函数,返回的值将用作缓存键。另外还有一个附加签名:
可调用(meta)
,其中meta
是一个类似dict的对象,具有一些附加属性–请参见下文。
timeout
–值将使用提供的超时进行缓存,基本上应该是秒数,但它取决于缓存后端类型。默认值是默认值
–内部常量意味着实际上没有为缓存后端提供值,因此后端应该决定使用什么超时。还支持可调用。标记
–字符串序列或可调用。应提供或返回添加到缓存值的标记的列表,以便稍后使用任何标记名使缓存无效。标记可能支持高级字符串格式语法。有关详细信息,请参见缓存键
文档和示例。
前缀
–此参数同时工作:作为常规标记和缓存键前缀,此处支持通常的高级字符串格式和可调用性。缓存别名
–缓存后端别名,它也可以是django缓存后端别名缓存实例
–可以直接提供缓存后端实例通过这个参数。
ecached_属性
应用于创建所谓的缓存属性,其签名与ecached的签名完全相同
简单示例
代码示例是显示此软件包功能的最佳方式。
decorators只能与默认参数一起使用
fromeasy_cacheimportecached,create_cache_key# default parameters# cache key will be generated automatically:## <__module__>.<__class__>.<function name> + function parameters converted to strings,## so be careful when using complex objects, it's# better to write custom cache key generator in such cases.## timeout will be default for specified cache backend# "default" cache backend will be used if you use Django@ecached()deftime_consuming_operation(*args,**kwargs):pass# simple static cache key and cache timeout 100 seconds@ecached('time_consuming_operation',100)deftime_consuming_operation():pass# cache key with advanced string formatting syntax@ecached('my_key:{b}:{d}:{c}')deftime_consuming_operation(a,b,c=100,d='foo'):pass# or@ecached('key:{kwargs[param1]}:{kwargs[param2]}:{args[0]}')deftime_consuming_operation(*args,**kwargs):pass# use specific cache alias, see "caches framework" belowfromfunctoolsimportpartialmemcached=partial(ecached,cache_alias='memcached')# equivalent to cache_key='{a}:{b}'@memcached(['a','b'],timeout=600)deftime_consuming_operation(a,b,c='default'):pass
使用自定义缓存密钥生成器
# working with parameters provided to cached function# cache key generator must have the same signature as decorated functionfromeasy_cacheimportcreate_cache_keydefcustom_cache_key(self,a,b,c,d):returncreate_cache_key(self.id,a,d)# working with `meta` objectdefcustom_cache_key_meta(meta):return'{}:{}:{}'.format(meta['self'].id,meta['a'],meta['d'])# or equivalentfromeasy_cacheimportmeta_accepted@meta_accepteddefcustom_cache_key_meta(parameter_with_any_name):meta=parameter_with_any_namereturn'{}:{}:{}'.format(meta['self'].id,meta['a'],meta['d'])classA(object):id=1@ecached(custom_cache_key)deftime_consuming_operation(self,a,b,c=10,d=20):...@ecached(custom_cache_key_meta)deftime_consuming_operation(self,a,b,c=10,d=20):...
如何正确缓存staticmethod和classmethod
# ecached decorator always comes topmostclassB(object):# cache only for each different year@ecached(lambdastart_date:'get_list:{}'.format(start_date.year))@staticmethoddefget_list_by_date(start_date):...CONST='abc'@ecached('info_cache:{cls.CONST}',3600,cache_alias='redis_cache')@classmethoddefget_info(cls):...
元可压缩对象描述
元对象具有以下参数:
args
–为修饰函数提供位置参数的元组kwargs
–为修饰函数提供关键字参数的字典返回值
–修饰函数返回的值,仅当在标记
或前缀
生成器中处理元对象时可用。在使用此参数之前,您必须检查是否已返回值
属性:
defgenerate_cache_key(meta):ifmeta.has_returned_value:# ... do something with meta.returned_value ...
调用参数
-提供所有位置参数和关键字参数的字典 要修饰函数,您还可以通过dict接口访问它们,例如meta['param1']
函数
-修饰可调用作用域
-已附加修饰可调用的对象,无
否则。通常是实例或类。
标记无效、刷新和缓存属性
基于标记的缓存失效允许您同时使多个缓存键失效。
假设您创建了一个基于Web的书店,您的用户可以将一本书标记为喜欢,因此您需要为每个用户维护一个喜欢的书的列表,但是,关于一本书的信息可能包含许多不同的数据,例如作者姓名、评级、库存可用性、外部服务的一些数据。等。
有些信息只能在运行时计算,因此您决定缓存喜欢的图书列表。
但是如果一本书的书名被更新了,我们必须找到存储这本书的所有缓存键并使它们失效。这样的任务可能很复杂,但是如果您用一个特定的标记标记了所有必需的缓存键,您只需要使标记无效,相关的缓存键将"自动"失效。
这里有更复杂的例子介绍django模型和有效的标签使用。 有关详细说明,请检查代码注释和文档字符串。
fromdjango.dbimportmodelsfromeasy_cacheimportecached,ecached_property,create_cache_keyclassBook(models.Model):title=models.CharField(max_length=250)def__unicode__(self):returnself.titleclassUser(models.Model):name=models.CharField(max_length=100)state=models.CharField(max_length=15,choices=(('active','active'),('deleted','deleted')),)friends=models.ManyToManyField('self',symmetrical=True)favorite_books=models.ManyToManyField('Book')def__unicode__(self):returnself.name@ecached('users_by_state:{state}',60,tags=['users_by_states'])@classmethoddefget_users_by_state(cls,state):""" Caches user list by provided state parameter: there will be separate cached value for every different state parameter, so we are having 2 different cache keys: users_by_state:active – cached list of active users users_by_state:deleted – cached list of deleted users Note that `ecached` decorator always comes topmost. To invalidate concrete cached state call the following method with the required `state`, e.g.: >>> User.get_users_by_state.invalidate_cache_by_key('active') ... removes `users_by_state:active` cache key or >>> User.get_users_by_state.invalidate_cache_by_key(state='deleted') ... removes `users_by_state:deleted` cache key If you'd like to invalidate all caches for all states call: >>> User.get_users_by_state.invalidate_cache_by_tags('users_by_states') ... removes both keys, since `users_by_states` tag attached to all of them, `invalidate_cache_by_tags` supports both string and list parameter types: >>> invalidate_cache_by_tags(['tag1', 'tag2', 'tag3']) To refresh concrete cached state call the following method with required `state`, e.g: >>> User.get_users_by_state.refresh_cache('active') ... calls `get_users_by_state('active')` and saves returned value to cache or >>> User.get_users_by_state.refresh_cache(state='deleted') """returnlist(cls.objects.filter(state=state))@ecached_property('user_friends_count:{self.id}',timeout=3600)deffriends_count(self):""" Caches friends count of each user for 1 hour. To access cache invalidation functions for a property you have to use class object instead of instance. Call the following method, to invalidate cache: >>> User.friends_count.invalidate_cache_by_key(user) ... removes cache key `user_friends_count:{user.id}` or >>> type(self).friends_count.invalidate_cache_by_key(user) or >>> self.__class__.friends_count.invalidate_cache_by_key(user) Where `user` is desired User instance to invalidate friends count for. Call the following method, to refresh cached data: >>> User.friends_count.refresh_cache(user) ... Updates `user.friends_count` in a cache. or >>> type(self).friends_count.refresh_cache(user) or >>> self.__class__.friends_count.refresh_cache(user) """returnself.friends.count()@staticmethoddefget_books_tags(meta):""" Add one tag for every book in list of favorite books. So we will add a list of tags to cached favorite books list. """ifnotmeta.has_returned_value:return[]favorite_books=meta.returned_value# yes, it may occupy a lot of cache keysreturn[create_cache_key('book',book.pk)forbookinfavorite_books]@ecached('user_favorite_books:{self.id}',600,get_books_tags)defget_favorite_books(self):""" Caches list of related books by user id. So in code you will use: >>> favorite_books = request.user.get_favorite_books() # cached for user You may want to invalidate this cache in two cases: 1. User added new book to favorites: >>> User.get_favorite_books.invalidate_cache_by_key(user) or >>> User.get_favorite_books.invalidate_cache_by_key(self=user) or >>> from easy_cache import invalidate_cache_key, create_cache_key >>> invalidate_cache_key(create_cache_key('user_favorite_books', user.id)) or >>> invalidate_cache_key('user_favorite_books:{}'.format(user.id)) 2. Some information about favorite book was changed, e.g. its title: >>> from easy_cache import invalidate_cache_tags, create_tag_cache_key >>> tag_cache_key = create_tag_cache_key('book', changed_book_id) >>> User.get_favorite_books.invalidate_cache_by_tags(tag_cache_key) or >>> invalidate_cache_tags(tag_cache_key) To refresh cached values use the following patterns: >>> User.get_favorite_books.refresh_cache(user) or >>> User.get_favorite_books.refresh_cache(self=user) """returnself.favorite_books.filter(user=self)
前缀用法
通常前缀
用于使一个命名空间中的所有缓存键失效,例如:
fromfunctoolsimportpartialclassShop(models.Model):single_shop_cache=partial(ecached,prefix='shop:{self.id}')@single_shop_cache('goods_list')defget_all_goods_list(self):return[...]@single_shop_cache('prices_list')defget_all_prices_list(self):return[...]# if you have `shop` object you are able to use the following invalidation# strategies:# Invalidate cached list of goods for concrete shopShop.get_all_goods_list.invalidate_cache_by_key(shop)# Refresh cached list of goods for concrete shopShop.get_all_goods_list.refresh_cache(shop)# Invalidate cached list of prices for concrete shopShop.get_all_prices_list.invalidate_cache_by_key(shop)# Refresh cached list of prices for concrete shopShop.get_all_prices_list.refresh_cache(shop)# Invalidate all cached items for concrete shopShop.get_all_goods_list.invalidate_cache_by_prefix(shop)# orShop.get_all_prices_list.invalidate_cache_by_prefix(shop)# orfromeasy_cacheimportinvalidate_cache_prefixinvalidate_cache_prefix('shop:{self.id}'.format(self=shop))
无效摘要
有两种方法可以使缓存对象失效:使用绑定到修饰函数的失效方法和单独的函数失效器。
pip install easy_cache0
这里的标记可以是字符串(单个标记)或标记列表。如果绑定方法用于缓存键/标记/前缀:
pip install easy_cache1
刷新摘要
有一种方法可以刷新缓存对象:使用绑定到修饰函数的刷新方法。
pip install easy_cache2
内部缓存框架
注意:内部缓存框架实例是单线程的,因此如果在一个线程中添加新的缓存实例,它将不会出现在另一个线程中。
easy cache默认使用内置django缓存框架,因此您可以选择在每个修饰函数上使用什么缓存存储,例如:
pip install easy_cache3
但是,如果不使用django,则在easy cache包中内置了缓存框架,它可以以与django缓存相同的方式使用:
pip install easy_cache4
已经实现了基于redis py client的redis缓存实例类
pip install easy_cache5
动态超时示例
您可能需要根据函数参数动态提供缓存超时:
pip install easy_cache6 < h2>发展与贡献
需要redis和memcached的实时实例才能通过少量测试,因此建议使用docker/docker compose来设置必要的环境:
pip install easy_cache7
性能和开销
可以使用tox
命令执行基准测试,它表明,在最坏的情况下,decorator提供大约4%的开销,平均大约1-2%的开销。
如果不使用标记或前缀,则在缓存中找不到结果时,将获得一个get
的缓存请求和一个set
的缓存请求,否则将发出两个连续的请求:get
和get\u many
以从缓存接收实际值并验证其tAGS(前缀)。然后执行一个"设置多个"请求,将数据保存到高速缓存中。