Python描述符协议详解
Python描述符协议详解一、什么是描述符描述符是实现了描述符协议的对象可以自定义属性的访问、赋值和删除行为。描述符协议包括__get__、__set__和__delete__方法。1.1 描述符的基本形式class Descriptor:def __get__(self, instance, owner):print(__get__ 被调用)def __set__(self, instance, value):print(__set__ 被调用)def __delete__(self, instance):print(__delete__ 被调用)class MyClass:attr Descriptor()obj MyClass()obj.attr # 调用 __get__obj.attr 10 # 调用 __set__del obj.attr # 调用 __delete__二、描述符的类型2.1 数据描述符同时定义了__get__和__set__方法的描述符。class DataDescriptor:def __get__(self, instance, owner):return instance.__dict__.get(_value, None)def __set__(self, instance, value):instance.__dict__[_value] value2.2 非数据描述符只定义了__get__方法的描述符。class NonDataDescriptor:def __get__(self, instance, owner):return 非数据描述符三、描述符方法的参数3.1 __get__(self, instance, owner)- self: 描述符实例本身- instance: 拥有描述符的实例类属性访问时为None- owner: 拥有描述符的类class MyDescriptor:def __get__(self, instance, owner):print(fself: {self})print(finstance: {instance})print(fowner: {owner})return valueclass MyClass:attr MyDescriptor()# 通过实例访问obj MyClass()obj.attr# instance 是 obj# owner 是 MyClass# 通过类访问MyClass.attr# instance 是 None# owner 是 MyClass3.2 __set__(self, instance, value)- self: 描述符实例- instance: 拥有描述符的实例- value: 要设置的值3.3 __delete__(self, instance)- self: 描述符实例- instance: 拥有描述符的实例四、属性查找顺序Python的属性查找遵循以下优先级1. 数据描述符定义了__set__的描述符2. 实例字典3. 非数据描述符只定义了__get__的描述符4. 类字典5. 父类字典class DataDesc:def __get__(self, instance, owner):return 数据描述符def __set__(self, instance, value):passclass NonDataDesc:def __get__(self, instance, owner):return 非数据描述符class MyClass:data DataDesc()non_data NonDataDesc()obj MyClass()obj.__dict__[data] 实例属性obj.__dict__[non_data] 实例属性print(obj.data) # 输出: 数据描述符优先级高print(obj.non_data) # 输出: 实例属性优先级高五、实战案例类型验证描述符class TypedProperty:def __init__(self, name, expected_type):self.name nameself.expected_type expected_typedef __get__(self, instance, owner):if instance is None:return selfreturn instance.__dict__.get(self.name)def __set__(self, instance, value):if not isinstance(value, self.expected_type):raise TypeError(f{self.name} 必须是 {self.expected_type.__name__} 类型)instance.__dict__[self.name] valueclass Person:name TypedProperty(name, str)age TypedProperty(age, int)def __init__(self, name, age):self.name nameself.age ageperson Person(Alice, 30)# person.age 30 # 抛出 TypeError六、实战案例延迟计算属性class LazyProperty:def __init__(self, func):self.func funcself.name func.__name__def __get__(self, instance, owner):if instance is None:return self# 计算值并缓存到实例字典value self.func(instance)instance.__dict__[self.name] valuereturn valueclass DataProcessor:def __init__(self, data):self.data dataLazyPropertydef processed_data(self):print(执行复杂计算...)return [x * 2 for x in self.data]processor DataProcessor([1, 2, 3])print(processor.processed_data) # 执行计算print(processor.processed_data) # 直接返回缓存值七、实战案例范围验证描述符class RangeValidator:def __init__(self, min_value, max_value):self.min_value min_valueself.max_value max_valuedef __set_name__(self, owner, name):self.name namedef __get__(self, instance, owner):if instance is None:return selfreturn instance.__dict__.get(self.name)def __set__(self, instance, value):if not self.min_value value self.max_value:raise ValueError(f{self.name} 必须在 {self.min_value} 到 {self.max_value} 之间)instance.__dict__[self.name] valueclass Product:price RangeValidator(0, 10000)quantity RangeValidator(0, 1000)def __init__(self, price, quantity):self.price priceself.quantity quantity八、__set_name__方法Python 3.6引入的__set_name__方法在描述符被赋值给类属性时自动调用。class NamedDescriptor:def __set_name__(self, owner, name):self.name nameprint(f描述符被命名为: {name})def __get__(self, instance, owner):if instance is None:return selfreturn instance.__dict__.get(self.name)def __set__(self, instance, value):instance.__dict__[self.name] valueclass MyClass:attr NamedDescriptor() # 输出: 描述符被命名为: attr九、property内置的描述符property是Python内置的描述符实现。class Circle:def __init__(self, radius):self._radius radiuspropertydef radius(self):return self._radiusradius.setterdef radius(self, value):if value 0:raise ValueError(半径不能为负数)self._radius valuepropertydef area(self):import mathreturn math.pi * self._radius ** 2circle Circle(5)print(circle.area) # 78.53981633974483circle.radius 10print(circle.area) # 314.1592653589793十、函数和方法描述符的应用Python中的函数也是描述符这就是为什么函数可以作为方法使用。class Function:def __init__(self, func):self.func funcdef __get__(self, instance, owner):if instance is None:return self# 返回绑定方法return lambda *args, **kwargs: self.func(instance, *args, **kwargs)class MyClass:Functiondef method(self, x):return f实例: {self}, 参数: {x}obj MyClass()print(obj.method(10))十一、classmethod和staticmethod这两个装饰器也是通过描述符实现的。class MyStaticMethod:def __init__(self, func):self.func funcdef __get__(self, instance, owner):return self.funcclass MyClassMethod:def __init__(self, func):self.func funcdef __get__(self, instance, owner):return lambda *args, **kwargs: self.func(owner, *args, **kwargs)十二、描述符的最佳实践1. 使用__set_name__自动获取属性名2. 在__get__中处理instance为None的情况3. 将数据存储在实例字典中避免无限递归4. 为描述符编写清晰的文档5. 考虑使用property作为简单场景的替代方案十三、描述符 vs property何时使用描述符- 需要在多个类中重用相同的属性逻辑- 需要复杂的验证或转换逻辑- 需要在类定义时执行某些操作何时使用property- 简单的getter/setter逻辑- 属性逻辑特定于单个类- 需要快速实现属性访问控制十四、总结描述符是Python中强大的特性它是property、classmethod、staticmethod等内置功能的基础。理解描述符协议可以帮助我们更好地理解Python的属性访问机制并创建可重用的属性管理逻辑。虽然描述符很强大但对于简单场景使用property通常就足够了。