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)时被触发。