在 python 引入新式类的同时,引进了很多新的概念,其中描述符就是一个。我个人认为描述符的概念有点难以理解,但是,却很重要,可以说是 python 新式类中属性和方法的核心要素,这篇文章就以个人的视角介绍一下描述符。

简介

描述符是 Python 新式类中的核心关键之一,它为新式类的对象属性和方法提供了强大的 API。那么描述符到底是什么呢?我认为描述符是表示对象属性的一个代理。

一个 property 的例子

和其他的人介绍描述符不一样,我首先先介绍一个例子,一个用 property 作为属性的例子,例子如下:

  1. def get_x(obj):
  2. print("get value of x")
  3. return obj._x
  4. def set_x(obj, value):
  5. print("ser value [{}] for x".format(value))
  6. obj._x = value
  7. class MyClass(object):
  8. x = property(get_x, set_x)
  9. if __name__ == "__main__":
  10. mc = MyClass()
  11. mc.x = 123
  12. print (mc.x)

执行一遍可以看到输出是:

  1. ser value [123] for x
  2. get value of x
  3. 123

如果你对 property 不熟悉的话,这里解释一下,从输出可以看出,对属性 x 设置值的时候调用的是 set_x 的函数,注意,这里说的是函数,不是方法;而获取属性 x 值的时候调用的是函数 get_x。从这里我们可以看出一点概念,之前所说的描述符是表示对象属性的一个代理,其实就是说操作 x 并不是真的操作了一个属性,而是操作的一些函数。

说回描述符

可以发现,之前定义 property 的时候是传了两个参数,其实 property 的声明是这样的:

  1. property(fget=None, fset=None, fdel=None, doc=None)

也就是说有三个函数可以传递,此外还有一个 doc。从第一个例子可以发现因为是函数,所以如果我们需要使用 property 定义一个属性的话,可能需要写三个函数,这样的话代码的结构会有点差,那么既然是新式类,有没有类的写法,答案是显然有的,我们可以这么玩:

  1. class RevealAccess(object):
  2. """A data descriptor that sets and returns values
  3. normally and prints a message logging their access.
  4. """
  5. def __init__(self, initval=None, name='var'):
  6. self.val = initval
  7. self.name = name
  8. def __get__(self, obj, objtype):
  9. print 'Retrieving', self.name
  10. return self.val
  11. def __set__(self, obj, val):
  12. print 'Updating', self.name
  13. self.val = val
  14. class MyClass(object):
  15. x = RevealAccess(10, 'var "x"')
  16. y = 5
  17. m = MyClass()
  18. print m.x
  19. m.x = 20
  20. print m.x
  21. print m.y

这段代码会输出:

  1. [[email protected].io]# python test.py
  2. Retrieving var "x"
  3. 10
  4. Updating var "x"
  5. Retrieving var "x"
  6. 20
  7. 5

这里其实我们就有一点自己定义了一个类型的意思了,其实发现描述符就是一个类,只不过这个类比较特殊,必须实现

中的一个或多个,这里有一个说法:

需要注意的一点就是方法/函数都是非数据描述符。

优先级别

那么区分数据描述符和非数据描述符有什么用?用处就是在访问对象的属性的时候,__getattribute__() 方法是以以下优先级进行访问数据的:

那么这个优先级有什么用?其中一个用处就是当在实例中有同名的属性和方法时,因为方法是非数据描述符,所以,我们访问的肯定是属性,而不是方法:

  1. class Test(object):
  2. def __init__(self):
  3. self.x = 123
  4. def x(self):
  5. print "function x"
  6. t = Test()
  7. print t.x
  8. del t.x
  9. print t.x

这段程序的结果应该是:

  1. 123
  2. <bound method Test.x of <__main__.Test object at 0x7f7b06707b90>>

__getattribute__ 方法

上面说了一下 __getattribute__ 是按优先级调用的,但是__getattribute__ 是如何调用的 __get__ 方法的呢?

对于一个类 X 和实例 x,我们调用 x.foo 由 __getattribute__ 转化成:

  1. type(x).__dict__['foo'].__get__(x, type(x))

如果调用的是 X.foo 的话,那应该是:

  1. X.__dict__['foo'].__get__(None, X)

Reference