PYTHON 进阶学习:描述器_python 描述性统计分析

定义

描述器作为 Python 编程语言中的一项高级特性,赋予了对象高度的自主性,使其能够自行定制属性的查找、存储以及删除操作。

核心方法如下:

方法

作用

__get__(self, obj, type=None)

定义获取属性时的行为(如 obj.attr)

__set__(self, obj, value)

定义设置属性值时的行为(如obj.attr=value)

__delete__(self, obj)

定义删除属性值时的行为(如del obj.attr)

只需实现这三个方法中的任意其一,该对象便足以被冠以描述器之名。

应用场景


在实际的业务开发场景中,我们甚少会自主定义描述器。然而,在整个开发进程里,描述器却频繁地被我们运用,其目的在于简化开发流程。诸如 @property、@staticmethod、@classmethod 等,均属于描述器的范畴。接下来,我们将通过一个常见的数据校验器实例展开阐述。

代码示例:

class IntValidator:
    """整形数据校验器"""
    def __set_name__(self, owner, name):
        self.name = name

    def __init__(self, min, max):
        self.min = min
        self.max = max

    def __set__(self, instance, value):
        if not isinstance(value, int):
            raise TypeError("必须是整数")
        if value < self.min or value > self.max:
            raise ValueError(f"必须在 {self.min} 和 {self.max} 之间")
        instance.__dict__[self.name] = value

class StringValidator:
    """字符串校验器"""
    def __set_name__(self, owner, name):
        self.name = name

    def __init__(self, min_length, max_length):
        self.min_length = min_length
        self.max_length = max_length

    def __set__(self, instance, value):
        if not isinstance(value, str):
            raise TypeError("必须是字符串")
        if not (self.min_length <= len(value) <= self.max_length):
            raise ValueError(f"字符串长度必须在 {self.min_length} 和 {self.max_length} 之间")
        instance.__dict__[self.name] = value

class User:
    age = IntValidator(1, 100)
    name = StringValidator(1,10)

user = User()
user.age = 25  # 正确
user.age = 150  # 错误,超出范围
user.name = "Alice" # 正确
user.name = "abcdedfghjklmn" # 错误,字符串长度超出范围

优先级

描述器类型

定义

数据描述器

一个描述器实现了__set__ 或 __delete__ 方法,称为数据描述器

非数据描述器

一个描述器仅实现了__get__,称为非数据描述器

数据描述器与非数据描述器的差异主要体现在优先级方面。当实例的字典(__dict__)中存在与描述器同名的条目(可将其理解为实例的属性)时,二者的优先级情况如下:

数据描述器 > 实例属性 > 非数据描述器

代码示例

class DataDescriptor:

    def __set__(self, instance, value):
        print(f"__set__...{instance}, {value}")

    def __get__(self, instance, owner):
        print(f"__get__...{instance}, {owner}")
        return "来自数据描述器的值"

class NonDataDescriptor:

    def __get__(self, instance, owner):
        print(f"__get__...{instance}, {owner}")
        return "来自非数据描述器的值"

class Test:
    data = DataDescriptor()
    non_data = NonDataDescriptor()

test = Test()
test.data = "赋值给实例字典的值"
test.non_data = "赋值给实例字典的值"

print(test.data)
print(test.non_data)

# 打印结果如下
# __set__...<__main__.Test object at 0x0000016E8C7870E0>, 赋值给实例字典的值
# __get__...<__main__.Test object at 0x0000016E8C7870E0>, <class '__main__.Test'>
# 来自数据描述器的值
# 赋值给实例字典的值

从上述示例的结果中能够清晰察见,类变量 data 和 non_data,其一为数据描述器,另一则是非数据描述器。在完成赋值操作之后,data 并未被替换,而 non_data 则被成功替换。

使用注意

从上述示例中我们不难发现,所定义的描述器变量皆为类变量。这是因为,若为实例变量,在执行 obj.attr(获取操作)以及 obj.attr = value(设置操作)时,并不会触发 __set__ 和 __get__ 方法。(需注意:Python 的属性查找机制会在类级别检查是否存在描述器。)