Python __get__, __set__, __delete__方法介绍
class Desc: def __init__(self, name: str): self.name = name def __get__(self, instance, owner): """ self 描述符自身 instance 拥有描述符的实例,即描述符所挂载的类所创建的实例;直接通过类访问描述符,则instance是None owner 拥有描述符的类,即描述符所挂载的类 """ if instance is None: # 通过类访问属性返回描述符本身是常用做法 return self # 这里可以做一些自定义操作, 比如记录日志,缓存结果等等. return instance.__dict__.get(self.name, "default value") def __set__(self, instance, value): """ self 描述符自身 instance 拥有描述符的实例,即描述符所挂载的类所创建的实例 value 要设置的值 """ # 这里可以做一些自定义操作,比如数据校验,记录日志等等. instance.__dict__[self.name] = value def __delete__(self, instance): """ self 描述符自身 instance 拥有描述符的实例,即描述符所挂载的类所创建的实例 """ # 这里可以做一些自定义操作, 比如记录日志等等,也可以直接报错,不让删除属性 del instance.__dict__[self.name] class TestCls: desc_attr = Desc("desc_attr") test = TestCls() test.desc_attr = "test" print(test.desc_attr) del test.desc_attr """ 实现了__get__,__set__,__delete__中任一个方法的类创建的实例可以叫做描述符. 描述符在使用时,只能挂载到类,而不能直接挂载到实例,否则上面的方法不生效. 解释: __get__方法:控制属性访问, 这个方法比较特殊,"实例.<name>" 或 "类.<name>" 时触发,通过类访问时,传入的instance是None __set__方法:控制属性赋值, 只能"实例.<name> = <value>"时触发,"类.<name> = <value>"时不触发,而是直接赋值 __delete__方法:控制属性删除, 只能"del 实例.<name>"时触发,"del 类.<name>"时不触发,而是直接删除 特殊机制: 如果描述符只实现了__get__方法,"实例.<name>"会优先从实例字典中取值(即"instance.__dict__"中),其次触发描述符的__get__方法取值. 如果描述符实现了__set__或__delete__方法, "实例.<name>"会优先触发__get__方法取值,除非描述符没有实现__get__方法,否则每次都会触发__get__方法. """ # 特殊用法, 当描述符类的__init__方法接收函数时,可以当做装饰器使用 class Desc1: def __init__(self, func): self.func = func self.name = func.__name__ def __get__(self, instance, owner): return self.func(instance) class TestCls1: @Desc1 def test_func(self): return "test_func" # 相当于test_func = Desc1(test_func) # 所以访问实例的test_func时,就会触发Desc1的__get__方法,从而执行真正的"test_func(self)" test1 = TestCls1() print(test1.test_func)
延迟计算与缓存示例
class LazyProperty: def __init__(self, func): self.func = func self.name = func.__name__ def __get__(self, instance, owner): if instance is None: return self print(f"Computing {self.name}") value = self.func(instance) instance.__dict__[self.name] = value return value class Circle: def __init__(self, radius): self.radius = radius @LazyProperty def area(self): return 3.14 * self.radius ** 2 c = Circle(5) print(c.area) # 第一次计算 print(c.area) # 直接从实例字典获取,不再计算
# 这个LazyProperty描述符就是只实现了__get__方法,
# 并且在首次计算后,会把方法名作为key, 首次计算结果作为value到实例字典中.
# 下次再通过"实例.方法名"的方式访问属性的时候,就会从实例字典中取到值,从而不触发描述符的__get__方法.
浙公网安备 33010602011771号