Decorator @Cached_property

Decorator @Cached_property

Intro

Defination: Decorator that converts a method with a single self argument into a property cached on the instance.

PYTHON
from functools import cached_propertyimport json5 as jsonclass ConfigLoader:    @cached_property    def config(self):        with open('settings.json', 'r') as f:            return json.load(f)config = ConfigLoader().configprint(config)

Principle

源码逻辑比较简单,当首次访问被装饰的属性时,执行原始方法,将计算结果存储在instance的 __dict__ 字典中,后续访问时直接返回缓存值,不再重新计算

Source Code

PYTHON
# python3.13 functools.py################################################################################### cached_property() - property result cached as instance attribute################################################################################_NOT_FOUND = object()class cached_property:    def __init__(self, func):        self.func = func        self.attrname = None        self.__doc__ = func.__doc__        self.__module__ = func.__module__    def __set_name__(self, owner, name):        # Automatically called at the time the owning class owner is created.        if self.attrname is None:            self.attrname = name        elif name != self.attrname:            raise TypeError(                "Cannot assign the same cached_property to two different names "                f"({self.attrname!r} and {name!r})."            )    def __get__(self, instance, owner=None):        # Automatically called when the property is accessed.        if instance is None:  # True when accessed through the class, e.g. MyClass.attr            return self        if self.attrname is None:            raise TypeError(                "Cannot use cached_property instance without calling __set_name__ on it.")        try:            cache = instance.__dict__        except AttributeError:  # not all objects have __dict__ (e.g. class defines slots)            msg = (                f"No '__dict__' attribute on {type(instance).__name__!r} "                f"instance to cache {self.attrname!r} property."            )            raise TypeError(msg) from None        val = cache.get(self.attrname, _NOT_FOUND)        if val is _NOT_FOUND:            val = self.func(instance)            try:                cache[self.attrname] = val            except TypeError:                msg = (                    f"The '__dict__' attribute on {type(instance).__name__!r} instance "                    f"does not support item assignment for caching {self.attrname!r} property."                )                raise TypeError(msg) from None        return val    __class_getitem__ = classmethod(GenericAlias)
View Mermaid diagram code
sequenceDiagram
    participant User as 代码
    participant Property as cached_property
    participant Dict as instance.__dict__

    Note over User,Dict: 首次访问属性
    User->>Property: 访问属性 (obj.attr)
    Property->>Dict: 检查是否有缓存值
    Dict-->>Property: 返回 _NOT_FOUND (无缓存)
    Property->>Property: 调用原始方法
    Property->>Dict: 存储结果
    Property-->>User: 返回结果

    Note over User,Dict: 后续访问属性
    User->>Dict: 访问属性 (obj.attr)
    Dict-->>User: 直接返回缓存值
    Note right of Dict: Python 查找属性时优先检查 __dict__

Scenarios

访问频繁、计算耗时,通常为只读属性

Danger

Note: 如果对数据进行写入缓存并不会自动失效,需要手动del/set缓存

Examples

CaseDescription
Cache ORM related fields将数据库查询的结果中存到实例中
Cache HTTP Request Object解析 JSON、表单数据等计算成本较高的属性
PYTHON
# 并非同一个module,但思路是一致的# Demo Ref: https://medium.com/@esatyilmaz/introduction-c1306df1a84cfrom django.utils.functional import cached_propertyclass Book(models.Model):    title = models.CharField(max_length=50)    author = models.ForeignKey(Author, on_delete=models.CASCADE)    @cached_property    def author_full_name(self):        return self.author.full_name

Decorator @Cached_property

https://vluv.space/cached_property/

作者

GnixAij

发布于

2025-06-18

更新于

2025-08-12

许可协议

评论