lru_cache 缓存

庆云10个月前技术文章249

Python 语法: @functools.lru_cache(maxsize=128, typed=False)


Least-recently-used 装饰器。Iru 最近最少使用、cache 缓存。


如果 maxsize 设置为 None,则禁用 LRU 功能,并且缓存可以无限制增长。当 maxsize 是二的幂时,LRU 功能执行得最好。


如果 typed 设置为 True,则不同类型的函数参数将单独缓存。例如,f(3) 和 f(3.0) 将被视为具有不同结果的不同调用。


1、简单实例

import functools
import time

@functools.lru_cache()    # add = functools.lru_cache()(add)
def add(x, y, z=2):       # add() = wrapper()
    time.sleep(z)
    return x + y

add(4, 5)    # 执行2秒
add(4, 5)    # 瞬间完成
add(5, 6)    # 执行2秒
add(5, 6)    # 瞬间完成
add(4, 5)    # 瞬间完成


分析缓存是如何实现的?

  • 缓存可以通过字典记录实参和返回值,当下次传入相同实参时,通过 hash 访问;

  • key 是什么?


2、lru_cache 本质分析

2.1 lru_cache 伪代码

def lru_cache(maxsize=128, typed=False):

    def decorating_function(user_function):
        wrapper = _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo)
        return update_wrapper(wrapper, user_function)    # return wrapper

    return decorating_function

def _lru_cache_wrapper(user_function, maxsize, typed, _CacheInfo):
  
    make_key = _make_key    # _make_key函数作用: hash 传入的实参
    
    def wrapper(*args, **kwds):
    
        key = make_key(args, kwds, t-ped)    # hash 实参作为 key
        result = user_function(*args, **kwds)

        return result

    return wrapper

2.2 key 是什么?

key 是传入的实参的组合,通过 _make_key 组织在一起。

# 分析源代码
def _make_key(args, kwds, typed,    # (4, 5, z=6)
             kwd_mark = (object(),),
             fasttypes = {int, str},
             tuple=tuple, type=type, len=len):

    key = args           # (4, 5)
    if kwds:    # {'z':6}
        key += kwd_mark  # (4, 5, object())
        for item in kwds.items():    # items遍历 ——> 二元组 : ('z', 6)
            key += item  # (4, 5, object(), 'z', 6)
    if typed:
        key += tuple(type(v) for v in args)
        if kwds:
            key += tuple(type(v) for v in kwds.values())
    elif len(key) == 1 and type(key[0]) in fasttypes:
        return key[0]
    return _HashedSeq(key)    # _HashedSeq 是什么?


class _HashedSeq(list):       # 我: _HashedSeq 是列表的子类

    __slots__ = 'hashvalue'

    def __init__(self, tup, hash=hash):
        self[:] = tup         # 切片赋值,右边为可迭代对象,self 为列表
        self.hashvalue = hash(tup)

    def __hash__(self):
        return self.hashvalue    # 相当于返回:hash(tuple(self))

2.3 _make_key 实例

from functools import _make_key

_make_key((1,), {}, False)           # 1
_make_key((1,), {'a':100}, False)    # [1, <object at 0x29b644d7e70>, 'a', 100]

# 再通过 class _HashedSeq(list) 进行元组包裹、哈希。

2.4 总结

lru_cache 缓存装饰器:

  1. 构造装饰器

  1. 将被包装函数拿进去

  1. 将所有实参 _make_key

  1. _make_key 放在 tuple

  2. 哈希


    3、Iru_cache 装饰器应用

    3.1 使用前提

    • 同样的函数参数一定得到同样的结果

    • 函数执行时间很长,且要多次执行

    • 本质是函数调用的参数 => 返回值

    3.2 缺点

    • 不支持缓存过期,key 无法过期、失效

    • 不支持清除操作

    • 不支持分布式,是一个单机的缓存

    3.3 适用场景及实例

    适用场景,单机上需要空间换时间的地方,可以用缓存来将计算变成快速的查询。cache 还可以通过预加载热点数据,使第一次也进行hash查询。


    实例:

    import functools
    @functools.lru_cache(maxsize=60)    # 空间换时间,递归也有深度限制
    def fib(n):
        return 1 if n < 3 else fib(n-1) + fib(n-2)
    
    fib(100)


    相关文章

    远程DEBUG HADOOP源码方法

    远程DEBUG HADOOP源码方法

    1. 安装IDEA2. 下载hadoop源码,必须与集群服务代码版本一致,否则会导致有的类无法找到3. 将源码导入IDEA工程并完成build4. 点击 菜单栏--运行--编辑配置 进行相关debug...

    MySQL运维实战(5.3) MySQL数据乱码的一些情况

    MySQL运维实战(5.3) MySQL数据乱码的一些情况

    表数据乱码当数据的真实编码和相关参数(常见的包括character_set_client, character_set_result, 字段的编码,终端的编码)不一致时,会产生乱码。测试1 - 表中的...

    Oracle数据库恢复演练

    1、演练目的验证核心系统数据库备份的有效性,在极端数据库故障情况下保证数据库存在一份可用的备份文件,为业务数据的安全提供保障。 2、演练准备提供一台2C16G本地60G的阿里ecs服务器,操...

    MySQL 8.0 新特性:invisible indexes

    MySQL 8.0 新特性:invisible indexes

    一、前言什么是 invisible indexes 呢?就是不可见索引,优化器会默认忽略的索引,关于这个特性的用处,需要我们一起挖掘。二、案例思考某客户研发提了一条删除索引的 SQL,这张表 15G,...

    Linux 文本三剑客 - Grep

    grep 是一个最初用于 Unix 操作系统的命令行工具。在给出文件列表或标准输入后,grep 会对匹配一个或多个正则表达式的文本进行搜索,并只输出匹配(或者不匹配)的行或文本。1970 年代,Uni...

    数据湖技术之iceberg(九)Spark与Iceberg整合写操作

    数据湖技术之iceberg(九)Spark与Iceberg整合写操作

    1. INSERT INTO"insert into"是向Iceberg表中插入数据,有两种语法形式:"INSERT INTO tbl VALUES (1,"z...

    发表评论    

    ◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。