什么是描述符

官方的定义:描述符是一种具有“捆绑行为”的对象属性。访问(获取、设置和删除)它的属性时,实际是调用特殊的方法(_get_(),_set_(),_delete_())。也就是说,如果一个对象定义了这三种方法的任何一种,它就是一个描述符。

描述符的作用是用来代理一个类的属性,需要注意的是描述符不能定义在被使用类的构造函数中,只能定义为类的属性,它只属于类的,不属于实例,我们可以通过查看实例和类的字典来确认这一点。

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

普通对象

示例

class Parent:
    name = "parent"

class Son(Parent):
    name = "son"

s = Son()
s.name = "other"
print(s.name)
------------------------------------------------------------------------
other

普通的python对象操作(增删改查)它的属性时,都是基于该对象的__dict__基础之上进行的。比如,上例中访问实例对象s的属性name: s.name的时候,查找顺序如下所示:

  1. 通过实例对象的__dict__属性访问: s.__dict__['name']
  2. 通过类型对象的__dict__属性访问: type(s).__dict__['name']等价于Son.__dict__['name']
  3. 通过父类对象的__dict__属性访问: s.__class__.__base__.__dict__['name']等价于Parent.__dict__['name']

类似地修改属性name的值也是通过__dict__的方式:

s.__dict__['name'] = 'zhangsan'
print s.name
------------------------------------------------------------------------
zhangsan

描述符

class Descriptor:
    def __init__(self, name):
        self.name = name
    
    def __get__(self, instance, owner):
        print("__get__")
        print("instance:", instance)
        print("owner:", owner)
        return self.name
    
    def __set__(self, instance, value):
        print("__set__", instance, value)
        self.name = value


class Man:
    name = Descriptor("张三")


someone = Man()
print(someone.name)
---------------------------------------------------------------------
__get__
instance: <__main__.Man object at 0x0000026B570D9E48>
owner: <class '__main__.Man'>
张三

这里的Descriptor就是一个描述符,访问Man对象的name属性时不再是通过__dict__属性来访问,而是通过调用Descriptor的__get__方法获取,同样的道理,给name赋值的时候是通过调用__set__方法实现而不是通过__dict__属性。

通过__dict__赋值,点号取值

someone.__dict__['name'] = '李四'
print(someone.name)
-----------------------------------------------------------------------
__get__
instance: <__main__.Man object at 0x0000026B570D9048>
owner: <class '__main__.Man'>
张三

通过点号赋值

someone.name = "王二"
print(someone.name)
------------------------------------------------------------------------
__set__ <__main__.Man object at 0x0000026B570D9D88> 王二
__get__
instance: <__main__.Man object at 0x0000026B570D9D88>
owner: <class '__main__.Man'>
王二

类似地,删除属性的值也是通过调用__delete__方法完成的。

数据描述符和非数据描述符

一个类,如果只定义了__get__ 方法,而没有定义__set__, __delete__ 方法,则认为是非数据描述符;反之,则成为数据描述符。
实例属性和类属性优先级高于非数据描述符,但低于数据描述符。

class Descriptor:
    def __init__(self, name):
        self.name = name
    
    def __get__(self, instance, owner):
        print("__get__")
        print("instance:", instance)
        print("owner:", owner)
        return self.name


class Man:
    name = Descriptor("张三")

m = Man()
# 实例属性
print("1:", m.name)
m.name = "李四"
print("2:", m.name)
print("3", Man.name)
Man.name = "王二"
print("4", Man.name)
-----------------------------------------------------------------------
__get__
instance: <__main__.Man object at 0x000001DDC4FDE1C8>
owner: <class '__main__.Man'>
1: 张三
2: 李四
__get__
instance: None
owner: <class '__main__.Man'>
3 张三
4 王二

属性查询优先级

  1. __getattribute__
  2. 数据描述符:由1触发调用,若人为的重载了该__getattribute__方法,可能会调职无法调用描述符
  3. 实例对象的字典
  4. 类的字典
  5. 非数据描述符
  6. 父类的字典
  7. __getattr__方法