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__方法.

 

posted @ 2025-07-21 13:49  CJTARRR  阅读(27)  评论(0)    收藏  举报