编写高质量代码:改善Python程序的91个建议这书里的建议59说的是“理解描述符”,描述符是descriptor的翻译。什么是descriptor,descriptor能用来做什么,其实Python的官方文档Descriptor HowTo Guide说得更简单易懂。在阅读完它们之后,自己也来尝试着总结记录一下。

Descriptor是一种约定

Python的descriptor的约定如下,

  • 定义了__get__、__set__、__delete__三个中任意一个的object即为descriptor
  • 定义了__get__、__set__的为data descriptor,否则为non-data descriptor

三个函数的函数签名如下,

__get__(self, obj, type=None)
__set__(self, obj, value)
__delete__(self, obj)

Descriptor如何发生作用

Python约定了descriptor里定义的几个函数如何被触发,

  • descriptor是在__getattribute__方法中被调用的

调用object.xxx与type.xxx触发的行为不一样,

  • object.xxx会触发object.__getattribute__,进而转换成object.__dict__['xxx'].__get__(object, type(object))
  • type.xxx会触发type.__dict__['xxx'].__get__(None, type)

看到descriptor的两种不同触发行为是不是就能够联想到Python中的staticmethod、classmethod。Descriptor HowTo Guide里给出了staticmethod、classmethod对应的Python实现,这里直接引用下,

class StaticMethod(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, objtype=None):
        return self.func
class ClassMethod(object):
    def __init__(self, func):
        self.func = func

    def __get__(self, obj, klass=None):
        if klass is None:
            klass = type(obj)
        def new_func(*args):
            return self.func(klass, *args)
        return new_func

除了object,class默认提供的__getattribute__之外,super()返回的对象也包含了__getattribute__方法,于是也会走descriptor的触发逻辑。

此外,Python的function、method的区别、bound method、unbound method也是以类似机制实现的。

Descriptor的注意事项

  • 只有new-style class(继承自object或type的类)才会触发descriptor行为
  • 重载了类的__getattribute__方法后会导致descriptor行为不会自动触发

总结

Descriptor的实质很简单,就是约定了某个对象上如果定义了相应的函数则其为descriptor,Python在调用__getattibute__时会对descriptor进行特定的处理。对于使用者来说,我们需要知道的就是这些特定处理的规则是怎样的,以及从已有的一些用例中寻找其合适的使用方式。

另外,如果面试需要考察Python相关知识的话,考察对descriptor的理解会是个不错的问题。

参考