Python描述器Descriptor
一. 描述器定义
一个描述器就是一个实现了三个核心属性访问的方法 __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