缓存是提升程序性能的关键技术——将频繁访问的「计算结果/数据」临时存储在高速介质(如内存)中,避免重复计算/重复查询(如数据库、api),从而大幅降低响应时间。以下是 python 缓存的入门指南,涵盖核心概念、常用实现方式和实战案例。
一、缓存的核心概念
1. 核心目标
减少「高耗时操作」的执行次数(如数据库查询、复杂计算、网络请求),用空间换时间。
2. 关键术语
- 缓存键(key):唯一标识缓存数据的索引(如函数参数、请求url);
- 缓存值(value):存储的目标数据(如查询结果、计算结果);
- 过期时间(ttl):缓存数据的有效期,避免数据过期;
- 缓存失效:缓存数据与源数据不一致(需设计失效策略,如过期、更新后主动删除);
- 缓存命中率:命中缓存的请求数 / 总请求数(越高性能越好)。
3. 适用场景
- 频繁调用的纯函数(输入固定则输出固定);
- 数据库/redis/api 等外部资源的重复查询;
- 复杂计算(如数据统计、机器学习模型推理);
- web 服务的接口响应(如接口返回的静态数据)。
二、python 缓存的常用实现方式
1. 方式1:手动实现缓存(字典)
最简单的缓存方式,用 python 字典存储键值对,适合小型脚本/简单场景。
示例:缓存斐波那契数列计算(避免重复递归)
# 手动缓存字典
fib_cache = {}
def fibonacci(n):
if n in fib_cache: # 命中缓存,直接返回
return fib_cache[n]
if n <= 1:
result = n
else:
result = fibonacci(n-1) + fibonacci(n-2) # 未命中,计算后存入缓存
fib_cache[n] = result
return result
# 测试:首次计算n=30(耗时),后续直接取缓存
print(fibonacci(30)) # 首次计算,存入缓存
print(fibonacci(30)) # 命中缓存,瞬间返回
优缺点
- 优点:极简、无依赖、灵活;
- 缺点:无过期时间、线程不安全、内存占用无限制(易内存泄漏)、程序重启后缓存丢失。
2. 方式2:functools.lru_cache(装饰器,python 内置)
python 标准库 functools 提供的「最近最少使用(lru)」缓存装饰器,专为函数缓存设计,自动管理缓存生命周期。
基础用法:缓存函数返回值
from functools import lru_cache
# @lru_cache 装饰器:缓存函数调用结果
# maxsize:缓存最大条目数(none 表示无限制),typed=true 区分不同类型参数(如 1 和 1.0)
@lru_cache(maxsize=none, typed=false)
def calculate_square(x):
print(f"计算 {x} 的平方(未命中缓存)")
return x * x
# 测试
print(calculate_square(5)) # 未命中,执行函数并缓存
print(calculate_square(5)) # 命中缓存,直接返回
print(calculate_square(10)) # 未命中,执行并缓存
# 查看缓存信息
print(calculate_square.cache_info()) # cacheinfo(hits=1, misses=2, maxsize=none, currsize=2)
# 清空缓存
calculate_square.cache_clear()
进阶:缓存带参数的数据库查询模拟
from functools import lru_cache
# 模拟数据库查询(高耗时操作)
def query_db(user_id):
print(f"查询数据库:user_id={user_id}")
return {"id": user_id, "name": f"user{user_id}", "age": 20 + user_id}
# 缓存查询结果(注意:装饰器仅缓存可哈希参数,列表/字典需转为元组/冻结集合)
@lru_cache(maxsize=100)
def get_user(user_id):
return query_db(user_id)
# 测试
print(get_user(1)) # 未命中,查询数据库
print(get_user(1)) # 命中缓存,直接返回
print(get_user(2)) # 未命中,查询数据库
优缺点
- 优点:内置无需安装、自动管理缓存(lru 淘汰策略)、支持缓存清空/信息查看;
- 缺点:无过期时间、仅支持函数缓存、程序重启后缓存丢失、不支持分布式(仅进程内)。
3. 方式3:第三方库(cachetools)—— 支持更多缓存策略
cachetools 是 python 第三方缓存库,扩展了 lru_cache 的能力,支持 ttl(过期时间)、lfu(最少使用)、fifo(先进先出)等策略。
步骤1:安装
pip install cachetools
步骤2:实战:带过期时间的缓存
from cachetools import ttlcache, cached
# 定义缓存规则:最大容量100,过期时间10秒(ttl)
cache = ttlcache(maxsize=100, ttl=10)
# 装饰器绑定缓存规则
@cached(cache)
def get_weather(city):
# 模拟调用天气api(高耗时)
print(f"调用天气api:{city}")
return {"city": city, "temperature": 25, "time": "2025-11-25"}
# 测试
print(get_weather("北京")) # 未命中,调用api
print(get_weather("北京")) # 命中缓存(10秒内)
# 10秒后再次调用:缓存过期,重新调用api
支持的缓存策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| ttlcache | 带过期时间的lru | 需定期更新数据(如天气) |
| lrucache | 最近最少使用淘汰 | 通用场景 |
| lfucache | 最少使用淘汰 | 访问频率不均的场景 |
| fifocache | 先进先出淘汰 | 顺序访问的场景 |
4. 方式4:分布式缓存(redis)—— 跨进程/跨机器共享
以上方式均为「进程内缓存」(仅当前程序可用),若需多进程/多机器共享缓存,需用 redis(高性能键值数据库)。
步骤1:安装依赖
# 安装redis驱动 pip install redis
步骤2:启动redis(本地/远程)
- 本地安装
- 测试可用:
redis-cli ping→ 返回pong。
步骤3:python 操作 redis 缓存
import redis
import time
# 连接redis(本地默认端口6379,无密码)
r = redis.redis(
host="localhost",
port=6379,
password="", # 若有密码请填写
db=0, # 选择数据库0
decode_responses=true # 自动将字节转为字符串
)
# 封装缓存函数
def get_data_from_cache(key, ttl=60):
"""从redis获取缓存,无则返回none"""
return r.get(key)
def set_data_to_cache(key, value, ttl=60):
"""存入redis缓存,设置过期时间(秒)"""
r.setex(key, ttl, value)
# 模拟业务函数:查询用户信息
def get_user_info(user_id):
# 先查缓存
cache_key = f"user:{user_id}"
cache_data = get_data_from_cache(cache_key)
if cache_data:
print(f"命中缓存:{cache_key}")
return cache_data
# 缓存未命中,查询数据库(模拟)
print(f"查询数据库:{cache_key}")
db_data = f"user {user_id} info"
# 存入缓存,过期时间60秒
set_data_to_cache(cache_key, db_data, ttl=60)
return db_data
# 测试
print(get_user_info(1001)) # 未命中,查数据库并缓存
print(get_user_info(1001)) # 命中缓存
time.sleep(61) # 等待缓存过期
print(get_user_info(1001)) # 缓存过期,重新查询
优缺点
- 优点:分布式共享、支持过期时间、持久化、高可用;
- 缺点:需部署redis服务、有网络开销、增加系统复杂度。
三、缓存的最佳实践
1. 选择合适的缓存粒度
- 细粒度:缓存单个数据(如单个用户信息),命中率高,失效影响小;
- 粗粒度:缓存聚合数据(如所有用户列表),命中率低,失效影响大。
2. 设置合理的过期时间
- 静态数据(如省份列表):过期时间设为几小时/几天;
- 动态数据(如用户余额):过期时间设为几秒/几分钟;
- 实时性要求高的数据(如秒杀库存):尽量不缓存,或缓存时间极短。
3. 避免缓存穿透
问题:查询不存在的数据(如 user_id=-1),缓存永远不命中,导致每次都查数据库,压垮数据库。
解决方案:缓存空值(如 r.setex("user:-1", 60, "null")),并设置短过期时间。
4. 避免缓存雪崩
问题:大量缓存同时过期,导致大量请求直接打向数据库。
解决方案:
- 过期时间加随机值(如
ttl=60 + random.randint(0, 10)); - 分布式缓存集群(如redis集群);
- 数据库加限流/熔断(如
ratelimit库)。
5. 缓存更新策略
- 写穿(write-through):更新数据库后立即更新缓存;
- 写回(write-back):先更新缓存,异步更新数据库(适合高并发写);
- 失效优先:更新数据库后删除缓存,下次查询时重新加载(最简单)。
四、缓存库选型建议
| 场景 | 推荐方案 |
|---|---|
| 简单函数缓存(单进程) | functools.lru_cache |
| 带过期时间的函数缓存 | cachetools.ttlcache |
| 分布式/跨进程缓存 | redis + redis 库 |
| web 框架集成(如flask) | flask-caching(封装redis/内存) |
| 高性能本地缓存 | pylibmc(基于memcached) |
五、总结
python 缓存入门的核心是:
- 优先用内置工具(
lru_cache)解决简单场景; - 需过期时间用
cachetools; - 分布式场景用 redis;
- 设计缓存时必须考虑「过期时间」「失效策略」「命中率」,避免缓存穿透/雪崩。
缓存是性能优化的「第一选择」,但不要过度设计——先通过性能测试找到瓶颈,再针对性加缓存,而非盲目为所有函数加缓存。
到此这篇关于python中缓存入门实战之核心概念与用法详解的文章就介绍到这了,更多相关python缓存内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论