已复制
全屏展示
复制代码

Python描述器Descriptor


· 3 min read

一. 描述器定义

一个描述器就是一个实现了三个核心属性访问的方法 __get__() 、__set__() 、 __delete__() 的类。常用作数据合法验证、缓存耗时计算等。

当一个对象的属性被访问时,Python 解释器首先检查该属性是否是一个描述器,如果该属性是描述器,则调用描述器对应的三个方法,操作属性值。如果属性不是描述器,则直接操作属性值。

  • __get__(self, instance, owner):用于获取属性值。如果访问属性的是一个实例,则 instance 参数是实例对象,owner 参数是类。如果访问属性的是一个类,则 instance 参数是None,owner 参数是类。
  • __set__(self, instance, value):用于设置属性值。如果设置属性值的是一个实例,则 instance 参数是实例对象,value 参数是要设置的值。如果设置属性值的是一个类,则 instance 参数是 None,value 参数是要设置的值。
  • __delete__(self, instance):用于删除属性值。如果删除属性值的是一个实例,则 instance 参数是实例对象。如果删除属性值的是一个类,则 instance 参数是None。

二. 数据合法验证

定义一个正数的描述器,当给它设置负数时抛出错误。

class PositiveInteger:
    def __get__(self, instance, owner):
        # instance 是 MyClass 的实例 obj
        # owner 就是 MyClass 类
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        # instance 是 MyClass 的实例 obj
        if not isinstance(value, int) or value <= 0:
            raise ValueError("Attribute value must be a positive integer")
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        # instance 是 MyClass 的实例 obj
        del instance.__dict__[self.name]

    def __set_name__(self, owner, name):
        # 创建 PositiveInteger 实例的时候,传入的 name 等于 my_attr
        self.name = name


class MyClass:
    # 将描述符应用于类属性
    my_attr = PositiveInteger()

    def __init__(self, my_attr):
        self.my_attr = my_attr


obj = MyClass(10)
print(obj.my_attr)  # 输出: 10
obj.my_attr = 20    # 正常设置属性值
obj.my_attr = -5    # 抛出异常: ValueError: Attribute value must be a positive integer

注意:描述器只能在类级别被定义,而不能为每个实例单独定义。因此,下面的代码是无法工作的

class MyClass:

    def __init__(self, my_attr):
        self.my_attr = PositiveInteger()
        self.my_attr = my_attr
        

三. 缓存耗时计算

用于缓存某个耗时较久的函数计算。下面的示例中,第一次计算耗时 5 秒钟,第二次计算直接获取值即可。

import time


class CachedProperty:
    def __init__(self, func):
        self.func = func
        self.name = func.__name__

    def __get__(self, instance, owner):
        if instance is None:
            return self
        value = self.func(instance)
        setattr(instance, self.name, value)  # 缓存属性值
        return value


class MyClass:
    @CachedProperty
    def expensive_calculation(self):
        print("计算中...")
        time.sleep(5)  # 模拟一个耗时的计算
        return 42


obj = MyClass()
start_time = time.time()
print(obj.expensive_calculation)  # 第一次调用,需要进行计算
print("首次计算耗时:", time.time() - start_time)  # 5.003308057785034

start_time = time.time()
print(obj.expensive_calculation)  # 第二次调用,从缓存中获取值
print("再次计算耗时:", time.time() - start_time)  # 1.811981201171875e-05
🔗

文章推荐