自己设计一个cache decorator

python的标准库里有一个functools.lru_cache,性能不错,因为是用C语言实现的,智能,因为不需要设置缓存key,自动根据函数参数生成uuid。然而,却并不实用。理由有:

1. 居然不支持修饰async def函数,而async def函数一般比较耗时,反而更需要cache

2. 尽管很智能,但不支持指定key,有时候我们只需按某个参数(比如用户ID,URL)为key即可,而不需要它如此智能地将所有参数都考虑进去。能指定key,也会方便偶尔需要clear cache的场景

3. LRU算法虽然有用,但好像用到的机会非常少

所以需要自己实现一个cache修饰器,满足如下两点:

1. 无论def还是async def都能修饰

2. 可以指定缓存key

实现如下:

from operator import setitem
def cache(key='{}'):
    def decorator(method):
        _cache = {}
        _waitable = asyncio.iscoroutinefunction(method)
        method.cache_clear = lambda: _cache.clear()
        @functools.wraps(method)
        def wrapper(*args, **kwargs):
            ckey = key.format(*args, **kwargs)
            if ckey in _cache:
                if _waitable:
                    f = asyncio.Future()
                    f.set_result(_cache[ckey])
                    return f
                else:
                    return _cache[ckey]

            res = method(*args, **kwargs)
            if _waitable:
                res = asyncio.get_event_loop().create_task(res)
                res.add_done_callback(lambda x: setitem(_cache, ckey, x.result()))
            else:
                _cache[ckey] = res
            return res
        return wrapper
    return decorator

特别说明:

1. 正如闭包的原理,_cache和_waitable被堆栈引用,不会被释放,所以无需挂载在任何函数或者全局变量下

2. 这里的key要求使用{}占位符表达式,这样使用str.format()格式化时不会报参数个数不匹配的错误

3. 据说官方之所以不支持的原因是asyncio.iscoroutinefunction()比较耗时,所以这里只调用了一次,并保存到闭包变量_waitable里

使用举例:

@cache('{}-{}')
async def fetch(schema, api, timeout):
    return await GET(schema + '://' + DOMAIN + api, timeout=timeout)

await fetch('http', '/api/user/detail/', 6000)
await fetch('http', '/api/user/detail/', 12000)

 

发表于 2020年05月18日 23:41   评论:0   阅读:1815  



回到顶部

首页 | 关于我 | 关于本站 | 站内留言 | rss
python logo   django logo   tornado logo