python使用描述符协议缓存属性

47 阅读1分钟

这是 Django 框架中的一个装饰器类,用于将方法转换为缓存属性

核心功能

cached_property 是一个描述符(descriptor),它将一个方法转换为属性,并且只在第一次访问时执行该方法,之后的访问会直接返回缓存的结果。


import time

# 这个装饰器将单参数方法(只有 self)转换为实例上的缓存属性。
class cached_property:
    """
    Decorator that converts a method with a single self argument into a
    property cached on the instance.

    A cached property can be made out of an existing method:
    (e.g. ``url = cached_property(get_absolute_url)``).
    """

    name = None

    @staticmethod
    def func(instance):
        raise TypeError(
            "Cannot use cached_property instance without calling "
            "__set_name__() on it."
        )

    def __init__(self, func):
        self.real_func = func
        self.__doc__ = getattr(func, "__doc__")

    def __set_name__(self, owner, name):
        if self.name is None:
            self.name = name
            self.func = self.real_func
        elif name != self.name:
            raise TypeError(
                "Cannot assign the same cached_property to two different names "
                "(%r and %r)." % (self.name, name)
            )

    def __get__(self, instance, cls=None):
        """
        Call the function and put the return value in instance.__dict__ so that
        subsequent attribute access on the instance returns the cached value
        instead of calling cached_property.__get__().
        """
        if instance is None:
            return self
        res = instance.__dict__[self.name] = self.func(instance)
        return res

class MyClass:
    @cached_property
    def expensive_operation(self):
        print("Computing...")
        time.sleep(5)
        return 100

obj = MyClass()
print(obj.expensive_operation)  # 第一次:打印 "Computing..." 并计算
print(obj.expensive_operation)  # 第二次:直接返回缓存结果,不打印 "Computing..."

工作原理

  1. 第一次访问: 触发 __get__ 方法,执行原函数,将结果存储在 instance.__dict__[name]
  2. 后续访问: Python 的属性查找机制会在 __dict__ 中找到值,直接返回,不再调用 __get__