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 的属性查找机制会在类级别检查是否存在描述器。)