Python/描述符

维基教科书,自由的教学读本
描述符(descriptor)是一种类。把实现了__get__()、__set__()和__delete__()中的其中任意一种方法的类称之为描述符。这3种方法称为描述符协议。

描述符的作用是用来构造出类的一个属性(property)。它只属于类的,不属于实例。描述符是Python类特性中最底层的数据结构的实现手段。经常被使用的@classmethod、@staticmethd、@property、甚至是__slots__等属性都是通过描述符来实现的。它是很多高级库和框架的重要工具之一。是使用到装饰器或者元类的大型框架中的一个非常重要组件。

使用描述符,可以让程序员在引用一个对象属性时自定义要完成的工作。本质上看,描述符就是一个类,只不过它定义了另一个类中属性的访问方式。换句话说,一个类可以将属性管理全权委托给描述符类。

如下示例一个描述符及引用描述符类的代码。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__()以外的方法的描述符称为非数据描述符

描述符的优先级的高低顺序:类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到的属性触发__getattr__()。所以,用className.VarName=value,则为类属性;用instanceName.VarName=value,则优先是描述符。

在每次查找属性时,描述符协议中的方法都由类对象的特殊方法 __getattribute__() 调用(注意不要和 __getattr__() 弄混)。也就是说,每次使用InstanceName.VarName(或者 getattr(InstanceName, VarName))的调用方式时,都会隐式地调用 __getattribute__(),它会按照下列顺序查找该属性:

  1. 验证该属性是否为类实例对象的数据描述符;
  2. 如果不是,就查看该属性是否能在类实例对象的 __dict__ 中找到;
  3. 最后,查看该属性是否为类实例对象的非数据描述符。