Python学习之-描述符详解

本文详细介绍了Python中的描述符,如何通过实现__get__,__set__,__delete__方法来控制属性的访问、设置和删除。文章还展示了描述符在类型检查、数据校验和缓存属性结果等场景的应用,并与Python内置的property进行比较,强调了描述符的灵活性和适用性。
摘要由CSDN通过智能技术生成

前言:

什么是描述符?
在Python中,描述符是对多个属性访问操作的方法集合的形式化。简而言之,描述符让你能够控制属性的访问、设置、删除等操作。Python的描述符是实现了特定协议的对象,这个协议包括__get__、__set__和__delete__方法。通过这些方法可以在属性访问时定义自定义行为,这为Python的属性访问提供了极高的灵活性。

1 描述符示例

假设我们想要定义一个类属性,这个属性在被访问时会打印日志。我们可以创建一个描述符来做这件事:

"""
# @Time    : 2024/4/19
# @Author  : Summer
# @File    : Descriptor
# @describe:
"""


class LoggedAccess:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, owner):
        value = instance.__dict__[self.name]
        print(f"Accessing {self.name}, Value: {value}")
        return value

    def __set__(self, instance, value):
        print(f"Updating {self.name}, New value: {value}")
        instance.__dict__[self.name] = value


# 使用描述符
class MyClass:
    # 创建描述符实例
    my_attr = LoggedAccess("my_attr")

    def __init__(self, value):
        # 给描述符赋值将调用__set__
        self.my_attr = value


# 测试代码
obj = MyClass(10)
# 访问属性,将触发__get__,打印日志  Updating my_attr, New value: 10
x = obj.my_attr
# 更新属性,将触发__set__,打印日志 Accessing my_attr, Value: 10
obj.my_attr = 20  # Updating my_attr, New value: 20

2 描述符使用场景

2.1 类型检查

描述符可以用于强制类型检查,确保属性值符合预期的类型。这在静态语言中很常见,但在Python这样的动态语言中,通过描述符来实现类型检查可以增加代码的健壮性。

#!/usr/bin/env python
# coding=utf-8
"""
# @Time    : 2024/4/19
# @Author  : Summer
# @File    : Descriptor
# @describe:
"""


class TypeCheck:
    def __init__(self, name, expected_type):
        self.name = name
        self.expected_type = expected_type

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not isinstance(value, self.expected_type):
            raise TypeError(f"Expected {self.expected_type}")
        instance.__dict__[self.name] = value


class MyClass:
    name = TypeCheck("name", str)
    age = TypeCheck("age", int)

    def __init__(self, name, age):
        self.name = name
        self.age = age


obj = MyClass("Alice", 30)  # Works fine
obj.age = "thirty"  # Raises TypeError: Expected <class 'int'>

2.2. 数据校验

描述符也可以用于数据校验,确保设置的属性值满足特定条件或约束。

#!/usr/bin/env python
# coding=utf-8
"""
# @Time    : 2024/4/19
# @Author  : Summer
# @File    : Descriptor
# @describe:
"""


class ValueConstraint:
    def __init__(self, name, minval, maxval):
        self.name = name
        self.minval = minval
        self.maxval = maxval

    def __get__(self, instance, owner):
        return instance.__dict__[self.name]

    def __set__(self, instance, value):
        if not (self.minval <= value <= self.maxval):
            raise ValueError(f"Value must be between {self.minval} and {self.maxval}")
        instance.__dict__[self.name] = value


class Person:
    age = ValueConstraint("age", 0, 150)

    def __init__(self, age):
        self.age = age  # Validates age


obj = Person(35)  # Works fine
obj.age = 200  # Raises ValueError: Value must be between 0 and 150

2.3 缓存属性的结果

当计算属性值代价较高时,可以使用描述符来缓存这个值。只有在第一次访问属性时计算值,并在后续访问时从缓存返回。

#!/usr/bin/env python
# coding=utf-8
"""
# @Time    : 2024/4/19
# @Author  : Summer
# @File    : Descriptor
# @describe:
"""


class CachedProperty:
    def __init__(self, method):
        self.method = method
        self.cache_name = f"_{method.__name__}"

    def __get__(self, instance, owner):
        if hasattr(instance, self.cache_name):
            return getattr(instance, self.cache_name)

        value = self.method(instance)
        setattr(instance, self.cache_name, value)
        return value


class ExpensiveObject:
    def __init__(self, value):
        self.value = value

    @CachedProperty
    def expensive_computation(self):
        # Simulate an expensive computation
        print("Computing...")
        return self.value * 2


obj = ExpensiveObject(42)
print(obj.expensive_computation)  # Computing... \n 84
print(obj.expensive_computation)  # 84 (No "Computing..." printed)

3 描述符与property的区别

实现方式:property是Python中的内置装饰器,用于创建可管理的属性,其底层其实是利用了描述符的机制。另一方面,描述符是一个更通用的机制,它让我们通过定义特定的方法(get, set, delete)来控制属性访问。

灵活性:描述符提供了更大的灵活性。你可以定义一个描述符类,这个类可以被多个属性或者不同的类重用。与此相比,每个property通常只用于单个属性。

用例:如果你只想简单地控制一个类的单个属性的访问,property可能是更简单的方式。如果你想要更通用的控制属性访问的方法,或者想要同样的行为应用于多个属性或类,使用描述符可能是更好的选择。

总之,虽然property为属性访问提供了便利的封装,但描述符提供了更为强大和灵活的机制来定制属性的行为,适用于复杂的场景。

  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

夏天Aileft

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值