0

0

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

聖光之護

聖光之護

发布时间:2025-11-18 12:10:02

|

750人浏览过

|

来源于php中文网

原创

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

本文探讨了在Python中如何高效管理对象中依赖于其他属性的昂贵计算属性,特别是在对象初始化期间避免不必要的计算和潜在错误。通过引入`functools.cached_property`,我们展示了一种优雅且Pythonic的方法,来自动缓存属性值,并在其依赖项发生变化时智能地使其失效,从而取代了手动管理初始化标志的复杂性,提升了代码的简洁性和性能。

面向对象编程中,我们经常会遇到这样的场景:一个类的某个属性(例如,一个total值)是根据其其他几个属性(例如,apple和orange的数量)计算得出的。如果这个计算过程非常耗时,并且该属性会被频繁读取,那么为了性能考虑,我们通常会选择缓存这个计算结果,而不是每次访问时都重新计算。然而,当这些依赖属性发生变化时,缓存的值需要被更新。更具挑战性的是,在对象初始化阶段,依赖属性可能尚未完全设置,此时尝试计算缓存值可能会导致AttributeError。

挑战:昂贵计算属性的缓存与更新

考虑一个抽象容器Container和其具体实现Basket的例子。Basket包含多种水果,并有一个total属性表示水果的总量。这个total的计算成本较高,因此我们希望在水果数量变化时才重新计算,并且在初始化过程中避免因部分属性未定义而导致的错误。

原始的设计思路可能是在__setattr__方法中检查属性是否属于依赖项,并在属性发生变化时触发compute_total方法。为了避免在初始化期间因属性未完全设置而引发错误,可能需要引入一个手动管理的_initialised标志:

立即学习Python免费学习笔记(深入)”;

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__ 中,后续访问将直接返回缓存值,而不再执行方法。

MaxAI
MaxAI

MaxAI.me是一款功能强大的浏览器AI插件,集成了多种AI模型。

下载

当依赖属性发生变化时,我们只需从实例的 __dict__ 中删除 cached_property 对应的键,即可使其缓存失效。下一次访问该属性时,cached_property 将重新计算并缓存新值。

实现细节

  1. 在 Container.__setattr__ 中使缓存失效: 当任何被 _fruits 列表标记为依赖项的属性被设置时,我们尝试从实例的 __dict__ 中删除 total 属性。如果 total 尚未被访问过(即尚未缓存),则 KeyError 会被捕获并忽略,这正是我们期望的行为。

  2. 在 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。

注意事项与总结

  1. 适用场景: cached_property 最适合那些计算成本高昂、结果相对稳定、且被频繁读取的属性。如果属性值频繁变化,或者计算成本很低,直接使用普通 @property 可能更简单。
  2. 缓存位置: cached_property 将计算结果存储在实例的 __dict__ 中。这意味着每个实例都有自己的缓存,并且缓存值会覆盖同名的描述符对象。
  3. 缓存失效: 使 cached_property 缓存失效的唯一方法是从实例的 __dict__ 中删除对应的键。
  4. 线程安全: cached_property 在并发访问时不是线程安全的。如果多个线程可能同时首次访问或在缓存失效后访问同一个 cached_property,可能会导致多次计算。在这种情况下,需要额外的同步机制

通过 functools.cached_property,我们能够以一种更加声明式和Pythonic的方式来管理昂贵计算属性的缓存和更新逻辑,避免了手动管理状态标志的复杂性,提高了代码的可读性和维护性。这种模式在处理数据模型中派生属性时尤其有用,能够有效地平衡性能与代码简洁性。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

749

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

635

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

758

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

618

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1262

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

705

2023.08.11

Java 项目构建与依赖管理(Maven / Gradle)
Java 项目构建与依赖管理(Maven / Gradle)

本专题系统讲解 Java 项目构建与依赖管理的完整体系,重点覆盖 Maven 与 Gradle 的核心概念、项目生命周期、依赖冲突解决、多模块项目管理、构建加速与版本发布规范。通过真实项目结构示例,帮助学习者掌握 从零搭建、维护到发布 Java 工程的标准化流程,提升在实际团队开发中的工程能力与协作效率。

10

2026.01.12

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 3万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号