一般来说,一个描述器是一个有“绑定行为”的对象属性(object attribute),它的访问控制被描述器协议方法重写。这些方法是 __get__(), __set__(), 和 __delete__() 。有这些方法的对象叫做描述器。

默认对属性的访问控制是从对象的字典里面(__dict__)中获取(get), 设置(set)和删除(delete)它。举例来说, a.x 的查找顺序是, a.__dict__['x'] , 然后 type(a).__dict__['x'] , 然后找 type(a) 的父类(不包括元类(metaclass)).如果查找到的值是一个描述器, Python就会调用描述器的方法来重写默认的控制行为。这个重写发生在这个查找环节的哪里取决于定义了哪个描述器方法。注意, 只有在新式类中时描述器才会起作用。(新式类是继承自 type 或者 object 的类)

描述器是强大的,应用广泛的。描述器正是属性, 实例方法, 静态方法, 类方法和 super 的背后的实现机制。描述器在Python自身中广泛使用,以实现Python 2.2中引入的新式类。描述器简化了底层的C代码,并为Python的日常编程提供了一套灵活的新工具。

描述器协议

descr.__get__(self, obj, type=None) --> value

descr.__get__(self, obj, value) --> None

descr.__delete__(self, obj) --> None

一个对象如果是一个描述器,被当做对象属性(很重要)时重写默认的查找行为。

如果一个对象同时定义了__get__和__set__,它叫data descriptor。仅定义了__get__的描述器叫non-data descriptor。

data descriptor和non-data descriptor区别在于: 相对于实例的字典的优先级,如果实例字典有与描述器具同名的属性,如果描述器是data descriptor,优先使用data descriptor。如果是non-data descriptor,优先使用字典中的属性。

class B(object):

def __init__(self):

self.name = 'mink'

def __get__(self, obj, objtype=None):

return self.name

class A(object):

name = B()

a = A()

print a.__dict__ # print {}

print a.name # print mink

a.name = 'kk'

print a.__dict__ # print {'name': 'kk'}

print a.name # print kk

这里B是一个non-data descriptor所以当a.name = 'kk'的时候,a.__dict__里会有name属性, 接下来给它设置__set__

def __set__(self, obj, value):

self.name = value

... do something

a = A()

print a.__dict__ # print {}

print a.name # print mink

a.name = 'kk'

print a.__dict__ # print {}

print a.name # print kk

因为data descriptor访问属性优先级比实例的字典高,所以a.__dict__是空的。

描述器的调用描述器可以直接这么调用: d.__get__(obj)

然而更常见的情况是描述器在属性访问时被自动调用。举例来说, obj.d 会在 obj 的字典中找 d ,如果 d 定义了 __get__ 方法,那么 d.__get__(obj) 会依据下面的优先规则被调用。

调用的细节取决于 obj 是一个类还是一个实例。另外,描述器只对于新式对象和新式类才起作用。继承于 object 的类叫做新式类。

对于对象来讲,方法 object.__getattribute__() 把 b.x 变成 type(b).__dict__['x'].__get__(b, type(b)) 。具体实现是依据这样的优先顺序:资料描述器优先于实例变量,实例变量优先于非资料描述器,__getattr__()方法(如果对象中包含的话)具有最低的优先级。完整的C语言实现可以在 Objects/object.c 中 PyObject_GenericGetAttr() 查看。

对于类来讲,方法 type.__getattribute__() 把 B.x 变成 B.__dict__['x'].__get__(None, B) 。用Python来描述就是:

def __getattribute__(self, key):

"Emulate type_getattro() in Objects/typeobject.c"

v = object.__getattribute__(self, key)

if hasattr(v, '__get__'):

return v.__get__(None, self)

return v

其中重要的几点:

描述器的调用是因为 __getattribute__()

重写 __getattribute__() 方法会阻止正常的描述器调用

__getattribute__() 只对新式类的实例可用

object.__getattribute__() 和 type.__getattribute__() 对 __get__() 的调用不一样

资料描述器总是比实例字典优先。

非资料描述器可能被实例字典重写。(非资料描述器不如实例字典优先)

super() 返回的对象同样有一个定制的 __getattribute__() 方法用来调用描述器。调用 super(B, obj).m() 时会先在 obj.__class__.__mro__ 中查找与B紧邻的基类A,然后返回 A.__dict__['m'].__get__(obj, A) 。如果不是描述器,原样返回 m 。如果实例字典中找不到 m ,会回溯继续调用 object.__getattribute__() 查找。(译者注:即在 __mro__ 中的下一个基类中查找)

注意:在Python 2.2中,如果 m 是一个描述器, super(B, obj).m() 只会调用方法 __get__() 。在Python 2.3中,非资料描述器(除非是个旧式类)也会被调用。 super_getattro() 的实现细节在: Objects/typeobject.c ,[del] 一个等价的Python实现在 Guido's Tutorial [/del] (译者注:原文此句已删除,保留供大家参考)。

以上展示了描述器的机理是在 object, type, 和 super 的 __getattribute__() 方法中实现的。由 object 派生出的类自动的继承这个机理,或者它们有个有类似机理的元类。同样,可以重写类的 __getattribute__() 方法来关闭这个类的描述器行为。

描述器例子下面的代码中定义了一个资料描述器,每次 get 和 set 都会打印一条消息。重写 __getattribute__() 是另一个可以使所有属性拥有这个行为的方法。但是,描述器在监视特定属性的时候是很有用的。

class RevealAccess(object):

"""A data descriptor that sets and returns values

normally and prints a message logging their access.

"""

def __init__(self, initval=None, name='var'):

self.val = initval

self.name = name

def __get__(self, obj, objtype):

print 'Retrieving', self.name

return self.val

def __set__(self, obj, val):

print 'Updating' , self.name

self.val = val

>>> class MyClass(object):

x = RevealAccess(10, 'var "x"')

y = 5

>>> m = MyClass()

>>> m.x

Retrieving var "x"

10

>>> m.x = 20

Updating var "x"

>>> m.x

Retrieving var "x"

20

>>> m.y

5

这个协议非常简单,并且提供了令人激动的可能。一些用途实在是太普遍以致于它们被打包成独立的函数。像属性(property), 方法(bound和unbound method), 静态方法和类方法都是基于描述器协议的。

python 描述器 详解_深入解析Python中的descriptor描述器的作用及用法相关推荐

  1. python json模块详解_深入解析Python编程中JSON模块的使用

    JSON编码支持的基本数据类型为 None , bool , int , float 和 str , 以及包含这些类型数据的lists,tuples和dictionaries. 对于dictionar ...

  2. python学习详解_深入解析Python小白学习【操作列表】

    1.遍历列表 需要对列表中的每个元素都执行相同的操作时,可使用for 循环: magicians = ['alice','david','carolina'] for magician in magi ...

  3. python的抽象类详解_第7.19节 Python中的抽象类详解:abstractmethod、abc与真实子类...

    第7.19节 Python中的抽象类详解:abstractmethod.abc与真实子类 一. 引言 前面相关的章节已经介绍过,Python中定义某种类型是以实现了该类型对应的协议为标准的,而不是以继 ...

  4. python协程详解_对Python协程之异步同步的区别详解

    一下代码通过协程.多线程.多进程的方式,运行代码展示异步与同步的区别. import gevent import threading import multiprocessing # 这里展示同步和异 ...

  5. python input函数详解_对Python3中的input函数详解

    下面介绍python3中的input函数及其在python2及pyhton3中的不同. python3中的ininput函数,首先利用help(input)函数查看函数信息: 以上信息说明input函 ...

  6. python json方法详解_详解python中的json的基本使用方法

    在Python中使用json的时候,主要也就是使用json模块,json是以一种良好的格式来进行数据的交互,从而在很多时候,可以使用json数据格式作为程序之间的接口. #!/usr/bin/env ...

  7. python six模块详解_对python中的six.moves模块的下载函数urlretrieve详解

    实验环境:windows 7,anaconda 3(python 3.5),tensorflow(gpu/cpu) 函数介绍:所用函数为six.moves下的urllib中的函数,调用如下urllib ...

  8. 正则表达式符号特殊详解_常用正则表达式_Java中正则表达式的使用

    正则表达式符号详解 限定符: 指定一个组件必须出现多少次才能满足. 1.使用 "*", "+", "?" 作为限定符: "*&qu ...

  9. python 描述器 详解_描述器使用指南

    Python 的面向对象功能是在基于函数的环境构建的.通过使用非数据描述器,这两方面完成了无缝融合. Functions stored in class dictionaries get turned ...

最新文章

  1. 模拟电路技术之基础知识(八)
  2. Html5必看:教你如何选择移动APP开发框架
  3. PPT 下载 | 神策数据朱德康:用户中台建设实践解析
  4. flume案例-文件数据采集-运行测试
  5. java重写6,java重写equals()方法和hashCode()方法
  6. 用asp.net画饼图
  7. Zull路由网关---SpringCloud
  8. go int 转string_go 变量传递和切片
  9. 曝微信低调上线“银行储蓄”服务,这是越来越像支付宝的节奏?
  10. JQuery判断元素是否存在
  11. mssql2000跟mssql2005共享问题
  12. 离散数学太多符号了_解决离散数学命题符号化问题的三种方法
  13. sql server2008密钥
  14. 根据地理坐标查询地标 城市名称 街道名称 地标建筑
  15. assimp android build,Windows环境下编译Assimp库生成Android可用的.so或.a文件
  16. 小米手机安装欧洲版系统(MIUI12) 详细安装教程
  17. FormData 对象上传二进制文件
  18. 微信小程序点播插件_5个常见的小程序插件功能,教你怎么用
  19. Mysql:报错:error while loading shared libraries: libaio.so.1:
  20. Stream流的常用方法以及代码练习

热门文章

  1. html css js速成_CSS速成课程
  2. 音乐雷达 shazam算法_具有10亿首Shazam音乐识别功能的数据可视化
  3. 平滑滤波器模板尺寸与平滑效果的关系_用PPT基础功能,教你打造发布会效果,这骚操作谁顶得住啊?...
  4. chrome查看md文件
  5. vs2010中Calendar控件的一些使用
  6. CV《神经风格转换》
  7. Python中利用parse_args与namespace来简化函数传参
  8. 使用函数自调用实现将局部变量转换成全局变量
  9. 面向消费者的自动文本分析(Automated Text Analysis for Consumer Research) 2017 JCR 论文阅读
  10. 傅里叶级数的数学推导