python descriptor 详解
descriptor简介
这三个特殊的函数签名是这样的:
object.
__get__
(self, instance, owner):return value
object.
__set__
(self, instance, value):return None
object.
__delete__
(self, instance): 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)
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应用场景
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.
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,把相同的逻辑封装到一个单独的类,使用起来方便多了。详细的示例可以参见这篇文章。
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 callvery complex_calc4950>>> second call4950
第一,在访问complex_calc的时候并没有使用函数调用(没有括号);
references
python descriptor 详解的更多相关文章
- Python闭包详解
Python闭包详解 1 快速预览 以下是一段简单的闭包代码示例: def foo(): m=3 n=5 def bar(): a=4 return m+n+a return bar >> ...
- [转] Python Traceback详解
追莫名其妙的bugs利器-mark- 转自:https://www.jianshu.com/p/a8cb5375171a Python Traceback详解 刚接触Python的时候,简单的 ...
- python 数据类型详解
python数据类型详解 参考网址:http://www.cnblogs.com/linjiqin/p/3608541.html 目录1.字符串2.布尔类型3.整数4.浮点数5.数字6.列表7.元组8 ...
- Python 递归函数 详解
Python 递归函数 详解 在函数内调用当前函数本身的函数就是递归函数 下面是一个递归函数的实例: 第一次接触递归函数的人,都会被它调用本身而搞得晕头转向,而且看上面的函数调用,得到的结果会 ...
- python线程详解
#线程状态 #线程同步(锁)#多线程的优势在于可以同时运行多个任务,至少感觉起来是这样,但是当线程需要共享数据时,可能存在数据不同步的问题. #threading模块#常用方法:'''threadin ...
- python数据类型详解(全面)
python数据类型详解 目录1.字符串2.布尔类型3.整数4.浮点数5.数字6.列表7.元组8.字典9.日期 1.字符串1.1.如何在Python中使用字符串a.使用单引号(')用单引号括起来表示字 ...
- Python Collections详解
Python Collections详解 collections模块在内置数据结构(list.tuple.dict.set)的基础上,提供了几个额外的数据结构:ChainMap.Counter.deq ...
- python生成器详解
1. 生成器 利用迭代器(迭代器详解python迭代器详解),我们可以在每次迭代获取数据(通过next()方法)时按照特定的规律进行生成.但是我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记 ...
- 转 python数据类型详解
python数据类型详解 目录 1.字符串 2.布尔类型 3.整数 4.浮点数 5.数字 6.列表 7.元组 8.字典 9.日期 1.字符串 1.1.如何在Python中使用字符串 a.使用单引号(' ...
随机推荐
- extract-text-webpack-plugin 作用、安装、使用
作用:该插件的主要是为了抽离css样式,防止将样式打包在js中引起页面样式加载错乱的现象 安装:插件安装命令如下: npm install extract-text-webpack-plugin -- ...
- 用PHP去掉文件头的Unicode签名(BOM)
<?php //此文件用于快速测试UTF8编码的文件是不是加了BOM,并可自动移除 //By Bob Shen $basedir="."; //修改此行为需要检测的目录,点表 ...
- 编程实践:使用java访问mySQL数据库
1.虚拟机安装mySQL 服务器, 宿主机分别使用navicat工具和java代码 访问mySQL,组网图如下: 2. 查看mySQL的服务器状态,如下: 3. 服务器上查看数据库和数据表内容如下: ...
- MySQL 性能优化--优化数据库结构之优化数据大小
MySQL性能优化--优化数据库结构之优化数据大小 By:授客 QQ:1033553122 尽量减少表占用的磁盘空间.通常,执行查询期间处理表数据时,小表占用更少的内存. 表列 l 尽可能使 ...
- 使用volley上传多张图片,一个参数对应多张图片,转载
https://my.oschina.net/u/1177694/blog/491834 原帖地址 而如果使用volley的话,因为请求数据那些都很简便,但遇到上传文件就麻烦那可不好,同时使用多个网络 ...
- Kotlin入门(15)独门秘笈之特殊类
上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...
- 前端路由简介以及vue-router实现原理
后端路由简介 路由这个概念最先是后端出现的.在以前用模板引擎开发页面时,经常会看到这样 http://www.xxx.com/login 大致流程可以看成这样: 浏览器发出请求 服务器监听到80端口( ...
- 通用addEventListener方法
假设我们需要为一个<a id="aEle" href="http://www.baidu.com" />添加点击事件处理函数, 一般情况是这样的: ...
- C语言开发的思考
维护过十万行代码的通信协议,自己从头开始开发过几万行的代码,步骤: 1.移植性.为移植性对数据类型做重新定义. 2.内存计数.不要直接使用malloc和free,而是给所有类型的内存申请定义类型,并计 ...
- javascript中的异步编程
正常情况下js都是顺序执行的,但是也有很多场景下实际上是异步操作: 1.定时器都是异步操作 2.事件绑定都是异步操作 3.AJAX中一般我们都采取异步操作(也可以同步) 4.回调函数可以理解为异步(不 ...