方法和属性的有用缓存装饰器

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_cache
0

这里的标记可以是字符串(单个标记)或标记列表。如果绑定方法用于缓存键/标记/前缀:

pip install easy_cache
1

刷新摘要

有一种方法可以刷新缓存对象:使用绑定到修饰函数的刷新方法。

pip install easy_cache
2

内部缓存框架

注意:内部缓存框架实例是单线程的,因此如果在一个线程中添加新的缓存实例,它将不会出现在另一个线程中。

easy cache默认使用内置django缓存框架,因此您可以选择在每个修饰函数上使用什么缓存存储,例如:

pip install easy_cache
3

但是,如果不使用django,则在easy cache包中内置了缓存框架,它可以以与django缓存相同的方式使用:

pip install easy_cache
4

已经实现了基于redis py client的redis缓存实例类

pip install easy_cache
5

动态超时示例

您可能需要根据函数参数动态提供缓存超时:

pip install easy_cache
6 < h2>发展与贡献

需要redis和memcached的实时实例才能通过少量测试,因此建议使用docker/docker compose来设置必要的环境:

pip install easy_cache
7

性能和开销

可以使用tox命令执行基准测试,它表明,在最坏的情况下,decorator提供大约4%的开销,平均大约1-2%的开销。

如果不使用标记或前缀,则在缓存中找不到结果时,将获得一个get的缓存请求和一个set的缓存请求,否则将发出两个连续的请求:getget\u many以从缓存接收实际值并验证其tAGS(前缀)。然后执行一个"设置多个"请求,将数据保存到高速缓存中。

欢迎加入QQ群-->: 979659372 Python中文网_新手群

推荐PyPI第三方库


热门话题
java Intellij和Eclipse无法找到库工件   java Mapbox Android Symbolayer重置筛选器   java如何在顶部显示特定的recyclerview项?   java如何在Hibernate中使用@Qualifier   我想计算特定文本webdriver java在多个页面上可用的HTML表中的数据   java捕获Spring MVC REST服务抛出的Jersey REST客户端的异常   java Hibernate flush()影响事务   密钥绑定Java密钥绑定   sonarqube java,sonar,圈复杂度   使用3DES在Java中加密,在SQL Oracle中解密   regex正则表达式在regex101上工作。com,但不是prod   JAVAsql。SQLException:ORA00600:内部错误代码,参数:[12811],[93233]   java H2数据库存储在哪里?   java如何避免在使用Jackson时从JSON字符串反序列化空JSON数组元素