Python/描述符
描述符(descriptor)是指实现了以下描述符协议中至少一个方法的类:
- __get__(self, instance, owner):访问属性时触发
- __set__(self, instance, value):设置属性时触发
- __delete__(self, instance):删除属性时触发
在 Python 中,描述符(Descriptor) 是一种底层机制,用于控制属性的访问方式。它是通过实现特定的魔法方法(__get__、__set__ 和 __delete__)来实现的。例如:
class MyDescriptor: def __get__(self, instance, owner): print("调用 __get__") return instance._value def __set__(self, instance, value): print("调用 __set__") instance._value = value class MyClass: attr = MyDescriptor() obj = MyClass() obj.attr = 42 # 调用 __set__ print(obj.attr) # 调用 __get__,输出 42 在通过类对象或者实例对象读取属性attr时,python调用了这个MyDescriptor()实例的__get__方法,给它3个实参值,其中第一个实参值是描述符对象本身,第二个实参值是MyClass实例对象(可以为None),第三个实参值是MyClass类对象。
描述符的应用场景:
- 实现@staticmethod、@classmethod、@property等内置装饰器语法糖。甚至是__slots__等的实现。
- 属性验证(如类型检查、范围限制)
- 惰性加载属性
- 缓存机制
- ORM 框架中的字段定义(如 Django 的 models.Field)
使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。
如下示例一个描述符及引用描述符类的代码。Descriptors类就是一个描述符,Person是使用描述符的类:
class Descriptors: def __init__(self, key, value_type): self.key = key self.value_type = value_type def __get__(self, instance, owner): print("执行Descriptors的get") return instance.__dict__[self.key] def __set__(self, instance, value): print("执行Descriptors的set") if not isinstance(value, self.value_type): raise TypeError("参数%s必须为%s"%(self.key, self.value_type)) instance.__dict__[self.key] = value def __delete__(self, instance): print("执行Descriptors的delete") instance.__dict__.pop(self.key) class Person: name = Descriptors("name", str) age = Descriptors("age", int) def __init__(self, name, age): self.name = name self.age = age person = Person("xiaoming", 15) print(person.__dict__) person.name person.name = "jone" print(person.__dict__) #输出: #执行Descriptors的set #执行Descriptors的set #{'name': 'xiaoming', 'age': 15} #执行Descriptors的get #执行Descriptors的set #{'name': 'jone', 'age': 15} - 至少实现了内置__set__()和__get__()方法的描述符称为数据描述符;
- 实现了除__set__()以外的方法的描述符称为非数据描述符。
@property 修饰的属性、数据描述符或非数据描述符定义的属性,都是在类体的顶层定义,而不是在 __init__ 等实例方法中赋值,所以它们都被保存在类对象的 __dict__ 中。
通过类对象或者属性对象使用一个属性时,查找这个属性的操作优先级从高到低顺序:
- 数据描述符
- 实例属性:显然在实例对象的__dict__中
- 非数据描述符
- 类属性
- 找不到的属性触发__getattr__()
所以,用className.VarName=value,则为类属性;用instanceName.VarName=value,则优先是数据描述符。
在每次查找obj.attr 属性时,都用类对象的特殊方法 __getattribute__(),调用obj.__getattribute__('attr'):
- 先在 obj 的类及其父类的 __dict__ 中查找名为 attr 的属性。如果找到,并且它是一个数据描述符(即定义了 __get__ 和 __set__),则调用该描述符的 __get__ 方法,直接返回。
- 如果没有数据描述符,则查找实例对象自身的 __dict__(即 obj.__dict__)。如果找到了,就直接返回实例属性的值。
- 如果实例属性没有找到,再查找类及其父类的 __dict__。如果找到,并且它是一个非数据描述符(只定义了 __get__),则调用其 __get__ 方法,返回其值。如果只是普通的类属性(不实现描述符协议),直接返回属性值。
- 如果以上都没有找到,则查找类是否定义了 __getattr__ 方法。如果有,则调用 __getattr__(self, attr)。如果没有定义 __getattr__,则抛出 AttributeError。
当执行 obj.attr = value,Python 实际调用 obj.__setattr__('attr', value)。在 object的__setattr__ 的默认实现:
- 先检查 obj 的类(及其父类)中有没有名为 attr 的描述符。如果有,并且它实现了 __set__(即是数据描述符),就调用 该描述符.__set__(obj, value)。
- 如果没有数据描述符,就把值放进 obj.__dict__。
类对象赋值的默认行为就是直接把属性写入类对象自身的 __dict__中,不会触发数据描述符的 __set__ 方法。描述符的 __set__ 只会在实例赋值(instance.attr = value)时被触发。