新闻中心
如何优雅地管理Python中昂贵计算属性的缓存与更新

本文探讨了在Python中如何高效管理对象中依赖于其他属性的昂贵计算属性,特别是在对象初始化期间避免不必要的计算和潜在错误。通过引入`functools.cached_pr
operty`,我们展示了一种优雅且Pythonic的方法,来自动缓存属性值,并在其依赖项发生变化时智能地使其失效,从而取代了手动管理初始化标志的复杂性,提升了代码的简洁性和性能。
在面向对象编程中,我们经常会遇到这样的场景:一个类的某个属性(例如,一个total值)是根据其其他几个属性(例如,apple和orange的数量)计算得出的。如果这个计算过程非常耗时,并且该属性会被频繁读取,那么为了性能考虑,我们通常会选择缓存这个计算结果,而不是每次访问时都重新计算。然而,当这些依赖属性发生变化时,缓存的值需要被更新。更具挑战性的是,在对象初始化阶段,依赖属性可能尚未完全设置,此时尝试计算缓存值可能会导致AttributeError。
挑战:昂贵计算属性的缓存与更新
考虑一个抽象容器Container和其具体实现Basket的例子。Basket包含多种水果,并有一个total属性表示水果的总量。这个total的计算成本较高,因此我们希望在水果数量变化时才重新计算,并且在初始化过程中避免因部分属性未定义而导致的错误。
原始的设计思路可能是在__setattr__方法中检查属性是否属于依赖项,并在属性发生变化时触发compute_total方法。为了避免在初始化期间因属性未完全设置而引发错误,可能需要引入一个手动管理的_initialised标志:
from abc import ABC, abstractmethod
class Container(ABC):
_fruits = []
def __init__(self):
self._initialised = False # 手动初始化标志
def __setattr__(self, name, value):
super().__setattr__(name, value)
# 仅在初始化完成后且依赖属性变化时触发计算
if name in self._fruits and hasattr(self, 'compute_total') and self._initialised:
self.compute_total()
@abstractmethod
def compute_total(self):
pass
class Basket(Container):
_fruits = ['apple', 'orange']
def __init__(self, apple, orange, color):
super(Basket, self).__init__()
self.apple = apple
self.orange = orange
self.color = color
self._total = 0
self.compute_total() # 初始化时首次计算
self._initialised = True # 标记初始化完成
@property
def total(self):
return self._total
def compute_total(self):
self._total = self.apple + self.orange这种方法虽然解决了问题,但引入了一个额外的_initialised标志,增加了代码的复杂性,并且不够“Pythonic”。我们需要一种更简洁、更自动化的机制来处理这类场景。
解决方案:利用 functools.cached_property
Python 3.8 引入的 functools.cached_property 装饰器为解决此类问题提供了一个优雅的方案。cached_property 类似于 property,但它会缓存其计算结果。首次访问时,被装饰的方法会被调用并计算出结果,然后该结果会被存储在实例的 __dict__ 中,后续访问将直接返回缓存值,而不再执行方法。
Whimsical
Whimsical推出的AI思维导图工具
182
查看详情
当依赖属性发生变化时,我们只需从实例的 __dict__ 中删除 cached_property 对应的键,即可使其缓存失效。下一次访问该属性时,cached_property 将重新计算并缓存新值。
实现细节
在 Container.__setattr__ 中使缓存失效: 当任何被 _fruits 列表标记为依赖项的属性被设置时,我们尝试从实例的 __dict__ 中删除 total 属性。如果 total 尚未被访问过(即尚未缓存),则 KeyError 会被捕获并忽略,这正是我们期望的行为。
在 Basket 中使用 @cached_property: 将 total 方法标记为 @cached_property。这样,total 的计算只会在首次访问时发生,或者在缓存被手动删除后再次访问时发生。
下面是使用 cached_property 优化后的代码实现:
from abc import ABC
from functools import cached_property
class Container(ABC):
_fruits = []
def __setattr__(self, name, value):
super().__setattr__(name, value)
# 如果设置的属性是依赖项,则尝试使 'total' 缓存失效
if name in self._fruits:
try:
del self.__dict__['total']
except KeyError:
# 'total' 尚未被访问或缓存,无需处理
pass
class Basket(Container):
_fruits = ['apple', 'orange']
def __init__(self, apple, orange, color):
super(Basket, self).__init__()
self.apple = apple
self.orange = orange
self.color = color
@cached_property
def total(self):
"""
计算并缓存水果总数。
这个方法只会在首次访问或缓存失效后被调用。
"""
print("Calculating total...") # 用于演示何时进行计算
out = self.apple + self.orange
return out
示例与验证
让我们通过一个简单的交互式会话来验证上述实现的行为:
# 创建一个Basket实例
b = Basket(apple=10, orange=5, color='red')
# 首次访问total,会触发计算
print(f"Initial total: {b.total}") # 输出: Calculating total... \n Initial total: 15
# 再次访问total,直接返回缓存值,不会触发计算
print(f"Second access total: {b.total}") # 输出: Second access total: 15
# 改变一个依赖属性 (apple)
b.apple = 12
# 此时,__setattr__会删除b.__dict__['total'],使缓存失效
# 再次访问total,会重新触发计算
print(f"Total after changing apple: {b.total}") # 输出: Calculating total... \n Total after changing apple: 17
# 改变另一个依赖属性 (orange)
b.orange = 8
# 再次访问total,会重新触发计算
print(f"Total after changing orange: {b.total}") # 输出: Calculating total... \n Total after changing orange: 20
# 改变一个非依赖属性 (color)
b.color = 'green'
# 再次访问total,不会触发计算,因为color不是_fruits中的依赖项
print(f"Total after changing color: {b.total}") # 输出: Total after changing color: 20从上面的输出可以看出,total属性的计算只在必要时发生,即首次访问或其依赖项改变后。在对象初始化期间,由于total属性在__init__中没有被访问,所以不会在依赖属性尚未完全设置时尝试计算,从而避免了潜在的AttributeError。
注意事项与总结
- 适用场景: cached_property 最适合那些计算成本高昂、结果相对稳定、且被频繁读取的属性。如果属性值频繁变化,或者计算成本很低,直接使用普通 @property 可能更简单。
- 缓存位置: cached_property 将计算结果存储在实例的 __dict__ 中。这意味着每个实例都有自己的缓存,并且缓存值会覆盖同名的描述符对象。
- 缓存失效: 使 cached_property 缓存失效的唯一方法是从实例的 __dict__ 中删除对应的键。
- 线程安全: cached_property 在并发访问时不是线程安全的。如果多个线程可能同时首次访问或在缓存失效后访问同一个 cached_property,可能会导致多次计算。在这种情况下,需要额外的同步机制。
通过 functools.cached_property,我们能够以一种更加声明式和Pythonic的方式来管理昂贵计算属性的缓存和更新逻辑,避免了手动管理状态标志的复杂性,提高了代码的可读性和维护性。这种模式在处理数据模型中派生属性时尤其有用,能够有效地平衡性能与代码简洁性。
以上就是如何优雅地管理Python中昂贵计算属性的缓存与更新的详细内容,更多请关注其它相关文章!
# 使其
# 南京市网站推广
# 地产全民营销车身推广语
# 黄骅网站建设企业
# 招聘网站现状和推广方向
# 汉服的推广营销ppt
# 潮州专业网站优化服务
# 商丘网站建设意见
# 浙江网站建设怎么操作
# 增城百度seo建议
# 网站优化流程文案范文
# 几个
# 的是
# 自己的
# 如何做
# python
# 并在
# 是在
# 会在
# 面向对象
# 首次
# red
# 同步机制
# 并发访问
# 面向对象编程
# apple
# ai
# access
# app
相关栏目:
【
科技资讯46185 】
【
网络学院92790 】
相关推荐:
Win10双系统截图高效法 截屏快捷键速记【技巧】
深入理解J*aScript中的B样条曲线与节点向量生成
小猿搜题在线学习页面在哪_小猿搜题在线学习中心入口
从OpenAI API响应中高效提取生成文本
taptap防沉迷怎么解除 taptap解除健康系统限制说明【2025最新】
Win10如何恢复误删的快捷方式_Win10重建常用软件快捷方式
Sublime怎么配置Nim语言环境_Sublime Nim代码高亮与补全
Windows10怎么开启夜间模式 Windows10系统设置调整色温与亮度缓解夜间用眼疲劳【教程】
海量存储:机器视觉智能化的核心基石
浏览器打开即用 美图秀秀网页版入口
J*aScriptWebpack优化_J*aScript构建工具实战
黑猫投诉统一入口官网 消费者权益保护投诉平台
在J*a中如何开发在线活动报名与管理系统_活动报名管理项目实战解析
J*aScript中管理异步API调用:确保操作顺序与数据一致性
AI抖音网页版免费视频入口 AI抖音网页端最新视频实时观看
Golang如何通过reflect操作map_Golang reflect map操作与遍历技巧
12306选座系统怎么选连座_12306选座多人连坐操作方法
邮编格式怎么匹配地址_根据邮编格式快速匹配详细地址的技巧
Lar*el递归关系中排除子孙节点的策略
单射、满射与双射的关系 一文理清所有逻辑
Go语言中Map存储的结构体如何调用指针方法:深入解析与实践
多闪网页版在线观看免费入口_多闪官网访问入口
Go语言JSON解析深度指南:动态访问与结构体映射实践
手机CPU怎么影响游戏体验_手机CPU对游戏性能的影响分析
提升屏幕阅读器对“m”时间单位的播报准确性:HTML与CSS组合解决方案
Angular中单选按钮的正确使用与常见陷阱解析
使用Python高效删除Word宏并转换DOCM为DOCX格式
Win10如何清理注册表垃圾 Win10注册表维护与优化指南【慎用】
抖音小游戏合成大西瓜免费秒玩入口链接 抖音小游戏热门合集秒玩网站
Python多线程中正确使用sigwait处理SIGALRM信号
2026年CSGO开箱网站推荐 CSGO开箱平台精选
PyTorch模型训练准确率不提升:诊断与修复常见指标计算错误
谷歌google账号怎么注册账号 谷歌账号注册官方流程
sublime怎么设置启动时打开的窗口_sublime会话管理与热退出
Python类型检查:优化关联可选属性的Mypy推断策略
批改网学生版PC登录 批改网官网登录系统入口
Go语言中高效处理x-www-form-urlencoded表单数据
AngularJS $http POST请求数据传递与Go后端接收实践
夸克浏览器图书入口 夸克手机浏览器阅读入口
如何在CSS中使用visited与link控制链接颜色_visited link伪类配合
汽水音乐在线版入口_汽水音乐网页播放手册
Win11怎么设置开机NumLock亮 Win11修改注册表InitialKeyboardIndicators值
Win10桌面图标出现小盾牌怎么办 Win10去除UAC图标教程【解决】
html两个JS只运行一个怎么办_让双JS在html中都运行方法【技巧】
Tabulator表格日期时间排序问题及自定义解决方案
顺丰快递查询系统 官方正版查询入口
Django模型中自动计算可用余额的实现方法
Vue.js 图片显示异常排查:理解应用挂载范围与DOM ID唯一性
漫蛙Manwa2官网入口地址分享 漫蛙漫画PC版永久访问通道
神庙逃亡小游戏在线玩 神庙逃亡小游戏入口


2025-11-18
浏览次数:次
返回列表