descriptor简介

  在python中,如果一个新式类定义了__get__, __set__, __delete__方法中的一个或者多个,那么称之为descriptor。descriptor有分为data descriptor与non-data descriptor, descriptor通常用来改变默认的属性访问(attribute lookup),这部分会在下一遍文章中介绍。注意 ,descriptor的实例是一定是的属性(class attribute)

  这三个特殊的函数签名是这样的:

  object.__get__(selfinstanceowner):return value
  object.__set__(selfinstancevalue):return None
  object.__delete__(selfinstance): return None

  

  下面的代码展示了简单的用法:
 
 # -*- coding: utf-8 -*-
class Des(object):
def __init__(self, init_value):
self.value = init_value def __get__(self, instance, typ):
print('call __get__', instance, typ)
return self.value def __set__(self, instance, value):
print ('call __set__', instance, value)
self.value = value def __delete__(self, instance):
print ('call __delete__', instance) class Widget(object):
t = Des(1) def main():
w = Widget()
print type(w.t)
w.t = 1
print w.t, Widget.t
del w.t if __name__=='__main__':
main()

  运行结果如下:

('call __get__', <__main__.Widget object at 0x02868570>, <class '__main__.Widget'>)
<type 'int'>

('call __set__', <__main__.Widget object at 0x02868570>, 1)

('call __get__', <__main__.Widget object at 0x02868570>, <class '__main__.Widget'>)
1 ('call __get__', None, <class '__main__.Widget'>)

1

('call __delete__', <__main__.Widget object at 0x02868570>)

  从输出结果可以看到,对于这个三个特殊函数,形参instance是descriptor实例所在的类的实例(w), 而形参owner就是这个类(widget)

  w.t 等价于 Pro.__get__(t, w, Widget).而Widget.t 等价于 Pro.__get__(t, None, Widget)

descriptor注意事项

  需要注意的是, descriptor的实例一定是类的属性,因此使用的时候需要自行区分实例。比如下面这个例子,我们需要保证以下属性不超过一定的阈值。

 class MaxValDes(object):
def __init__(self, inti_val, max_val):
self.value = inti_val
self.max_val = max_val def __get__(self, instance, typ):
return self.value def __set__(self, instance, value):
self.value= min(self.max_val, value) class Widget(object):
a = MaxValDes(0, 10) if __name__ == '__main__':
w0 = Widget()
print 'inited w0', w0.a
w0.a = 123
print 'after set w0',w0.a
w1 = Widget()
print 'inited w1', w1.a

  代码很简单,我们通过MaxValDes这个descriptor来保证属性的值不超过一定的范围。运行结果如下:

inited w0 0
after set w0 10
inited w1 10

  可以看到,对w0.a的赋值符合预期,但是w1.a的值却不是0,而是同w0.a一样。这就是因为,a是类Widget的类属性, Widget的实例并没有'a'这个属性,可以通过__dict__查看。

  那么要怎么修改才符合预期呢,看下面的代码:

 class MaxValDes(object):
def __init__(self, attr, max_val):
self.attr = attr
self.max_val = max_val def __get__(self, instance, typ):
return instance.__dict__[self.attr] def __set__(self, instance, value):
instance.__dict__[self.attr] = min(self.max_val, value) class Widget(object):
a = MaxValDes('a', 10)
b = MaxValDes('b', 12)
def __init__(self):
self.a = 0
self.b = 1 if __name__ == '__main__':
w0 = Widget()
print 'inited w0', w0.a, w0.b
w0.a = 123
w0.b = 123
print 'after set w0',w0.a, w0.b w1 = Widget()
print 'inited w1', w1.a, w1.b

  运行结果如下:

inited w0 0 1
after set w0 10 12
inited w0 0 1

  可以看到,运行结果比较符合预期,w0、w1两个实例互不干扰。上面的代码中有两点需要注意:

  第一:第7、10行都是通过instance.__dict__来取值、赋值,而不是调用getattr、setattr,否则会递归调用,死循环。

  第二:现在类和类的实例都拥有‘a’属性,不过w0.a调用的是类属性‘a',具体原因参见下一篇文章

descriptor应用场景

  其实从上面的例子可以看出,descriptor主要用于控制属性的访问(读、写、删除)。python doc里面有写到,property()就是一个data descriptor实现(可参见这个文档)。 python2.2中,大量新式类的实现都基于descriptor  

They are the mechanism behind properties, methods, static methods, class methods, and super(). They are used throughout Python itself to implement the new style classes introduced in version 2.2.

 
  在实践中,我们有可能需要监控或者限制对属性的访问。比如,对象的一个属性被“莫名其妙”地修改了,但搜索所有文件有找不到可以的地方,那么我们可以通过__setattr__(self, k, v)方法,对于我们关心的 k 打印出调用栈。另外,也可以用property,示例代码如下:
 class TestProperty(object):
def __init__(self):
self.__a = 1 @property
def a(self):
return self.__a @a.setter
def a(self, v):
print('output call stack here')
self.__a = v if __name__=='__main__':
t = TestProperty()
print t.a
t.a = 2
print t.a

  如果需要禁止对属性赋值,或者对新的值做检查,也很容易修改上面的代码实现

  既然有了property,那什么时候还需要descriptor呢?property最大的问题在于不能重复使用,即对每个属性都需要property装饰,代码重复冗余。而使用descriptor,把相同的逻辑封装到一个单独的类,使用起来方便多了。详细的示例可以参见这篇文章

  笔者之前看bottle.py源码的时候,看到这么一个descriptor使用,部分源代码和测试代码如下:
 
 import functools, time
class cached_property(object):
""" A property that is only computed once per instance and then replaces
itself with an ordinary attribute. Deleting the attribute resets the
property. """ def __init__(self, func):
functools.update_wrapper(self, func)
self.func = func def __get__(self, obj, cls):
if obj is None: return self
value = obj.__dict__[self.func.__name__] = self.func(obj)
return value class TestClz(object):
@cached_property
def complex_calc(self):
print 'very complex_calc'
return sum(range(100)) if __name__=='__main__':
t = TestClz()
print '>>> first call'
print t.complex_calc
print '>>> second call'
print t.complex_calc
  运行结果如下:
     >>> first call
    very complex_calc
    4950
    >>> second call
    4950
 
  注意两点:

  第一,在访问complex_calc的时候并没有使用函数调用(没有括号);

  第二,第一次调用的时候打印了“very complex_calc”,第二次没有。
 
  笔者也是因为这段代码开始学习descriptor,但看懂这段代码还需要了解Python的属性查找顺序,下一篇文章会对此简单介绍。
 

references

(0)Implementing Descriptors, python2.7 doc
(2)Python描述符(descriptor)解密, http://www.geekfan.net/7862/

python描述符 descriptor的更多相关文章

  1. Python 描述符(descriptor) 杂记

    转自:https://blog.tonyseek.com/post/notes-about-python-descriptor/ Python 引入的“描述符”(descriptor)语法特性真的很黄 ...

  2. python描述符descriptor(一)

    Python 描述符是一种创建托管属性的方法.每当一个属性被查询时,一个动作就会发生.这个动作默认是get,set或者delete.不过,有时候某个应用可能会有 更多的需求,需要你设计一些更复杂的动作 ...

  3. python描述符(descriptor)、属性(property)、函数(类)装饰器(decorator )原理实例详解

     1.前言 Python的描述符是接触到Python核心编程中一个比较难以理解的内容,自己在学习的过程中也遇到过很多的疑惑,通过google和阅读源码,现将自己的理解和心得记录下来,也为正在为了该问题 ...

  4. Python描述符 (descriptor) 详解

    1.什么是描述符? python描述符是一个“绑定行为”的对象属性,在描述符协议中,它可以通过方法重写属性的访问.这些方法有 __get__(), __set__(), 和__delete__().如 ...

  5. Python 描述符(Descriptor) 附实例

    在 Python 众多原生特性中,描述符可能是最少被自定义的特性之一,但它在底层实现的方法和属性却无时不刻被使用着,它优雅的实现方式体现出 Python 简洁之美. 定义 一个描述符是一个有" ...

  6. Python 描述符 (descriptor)

    1.什么是描述符? 描述符是Python新式类的关键点之一,它为对象属性提供强大的API,你可以认为描述符是表示对象属性的一个代理.当需要属性时,可根据你遇到的情况,通过描述符进行访问他(摘自Pyth ...

  7. python描述符descriptor(二)

    python内置的描述符 python有些内置的描述符对象,property.staticmethod.classmethod,python实现如下: class Property(object): ...

  8. 【python】描述符descriptor

    开始看官方文档,各种看不懂,只看到一句Properties, bound and unbound methods, static methods, and class methods are all ...

  9. 杂项之python描述符协议

    杂项之python描述符协议 本节内容 由来 描述符协议概念 类的静态方法及类方法实现原理 类作为装饰器使用 1. 由来 闲来无事去看了看django中的内置分页方法,发现里面用到了类作为装饰器来使用 ...

随机推荐

  1. 安卓 eclipse项目创建

    一. 创建项目工程 1.  点击 file -> new -> Android Application project -> 2.  创建工程项目名字   自己命名 (注: 不要出现 ...

  2. shell下 使用心得

    打印时间的命令 date +'%F %k:%M:%d' crontab启动计划任务,注意两件事情: 1)crontab里启动的脚本,如果需要读取文件,需要使用绝对路径,或者在脚本里cd到目录所在的绝对 ...

  3. LeetCode_Generate Parentheses

    Given n pairs of parentheses, write a function to generate all combinations of well-formed parenthes ...

  4. TVS_压敏电阻等保护类器件的布局问题

    世上本没有垃圾,垃圾都是放错了位置的资源. 对于电路来说,保护器件就是其保护作用的关键资源,但如果放错了位置,它就是垃圾:甚至不仅是垃圾,而还可能成为祸害. 由最近承接的几起电路原理图审核项目来看,触 ...

  5. EXTJS4:在grid中加入合计行

    extjs4很方便的实现简单的合计(针对在不分页的情况下): 它效果实现在:Ext.grid.feature.Summary这个类中 Ext.define('TestResult', { extend ...

  6. 在微软平台上运行 SAP 应用程序

     本博客介绍了在微软平台上运行 SAP 应用程序的相关信息,作者在基于微软平台使用 SAP 方面有着数十年经验. 发布关于 Azure 的 SAP 说明 几个月前,SAP 针对适用于 SAP 软件 ...

  7. EL表达式使用之类switch语句

    http://blacksonny.iteye.com/admin/blogs/1879878

  8. table表格边框样式

    ; border-left:1px solid #aaa; border-top:1px solid #aaa; } td{border-right:1px solid #aaa; border-bo ...

  9. BeyondCompare两个文件中同一行字符长度不一致的文件对比,比如pi文件对比(xjl456852原创)

    假设有两个文件,里面存放的数字都只有一行,但长度不一样,对比时会有问题 示例文件: 对比示例如图: 左边的pi的字符串比较长,右边的比较短. 右边的pi的值不是从开始的第一个位置对比的,这样的情况是有 ...

  10. 小米路由器mini建FTP

    输入命令 df -h 复制代码 查看自己的分区路径,我自己用了一个闲置笔记本电脑硬盘转USB,插到mini,有四个区 为了方便,我选择把ftp安装在第一个分区  /extdisks/sda1/ 创建f ...