Python黑魔法@property装饰器的使用技巧解析


Posted in Python onJune 16, 2016

@property有什么用呢?表面看来,就是将一个方法用属性的方式来访问.
上代码,代码最清晰了.

class Circle(object): 
  def __init__(self, radius): 
    self.radius = radius 
 
  @property 
  def area(self): 
    return 3.14 * self.radius ** 2 
 
c = Circle(4) 
print c.radius 
print c.area

可以看到,area虽然是定义成一个方法的形式,但是加上@property后,可以直接c.area,当成属性访问.
现在问题来了,(不是挖掘机技术哪家强),每次调用c.area,都会计算一次,太浪费cpu了,怎样才能只计算一次呢?这就是lazy property.

class lazy(object): 
  def __init__(self, func): 
    self.func = func 
 
  def __get__(self, instance, cls): 
    val = self.func(instance) 
    setattr(instance, self.func.__name__, val) 
    return val 
 
class Circle(object): 
  def __init__(self, radius): 
    self.radius = radius 
 
  @lazy 
  def area(self): 
    print 'evalute' 
    return 3.14 * self.radius ** 2 
 
c = Circle(4) 
print c.radius 
print c.area 
print c.area 
print c.area

可以看到,'evalute'只输出了一次.如果看了我前面几篇博文,对@lazy的机制应该很好理解.
在这里,lazy类有__get__方法,说明是个描述器,第一次执行c.area的时候,因为顺序问题,先去c.__dict__中找,没找到,就去类空间找,在类Circle中,有area()方法,于是就被__get__拦截.
在__get__中,调用实例的area()方法算出结果,并动态给实例添加个同名属性把结果赋给它,即加到c.__dict__中去.
再次执行c.area的时候,先去c.__dict__找,因为此时已经有了,就不会经过area()方法和__get__了.

注意点
请注意以下代码场景:

代码片段1:
Python2.6代码 

class Parrot(object): 
  def __init__(self): 
    self._voltage = 100000 
 
  @property 
  def voltage(self): 
    """Get the current voltage.""" 
    return self._voltage 
 
if __name__ == "__main__": 
  # instance 
  p = Parrot() 
  # similarly invoke "getter" via @property 
  print p.voltage 
  # update, similarly invoke "setter" 
  p.voltage = 12

代码片段2:
Python2.6代码 

class Parrot: 
  def __init__(self): 
    self._voltage = 100000 
 
  @property 
  def voltage(self): 
    """Get the current voltage.""" 
    return self._voltage 
 
if __name__ == "__main__": 
  # instance 
  p = Parrot() 
  # similarly invoke "getter" via @property 
  print p.voltage 
  # update, similarly invoke "setter" 
  p.voltage = 12

代码1、2的区别在于

class Parrot(object):

在python2.6下,分别运行测试
片段1:将会提示一个预期的错误信息 AttributeError: can't set attribute
片段2:正确运行

参考python2.6文档,@property将提供一个ready-only property,以上代码没有提供对应的@voltage.setter,按理说片段2代码将提示运行错误,在python2.6文档中,我们可以找到以下信息:

BIF:
property([fget[, fset[, fdel[, doc]]]])
Return a property attribute for new-style classes (classes that derive from object).
原来在python2.6下,内置类型 object 并不是默认的基类,如果在定义类时,没有明确说明的话(代码片段2),我们定义的Parrot(代码片段2)将不会继承object

而object类正好提供了我们需要的@property功能,在文档中我们可以查到如下信息:

new-style class
Any class which inherits from object. This includes all built-in types like list and dict. Only new-style classes can use Python's newer, versatile features like __slots__, descriptors, properties, and __getattribute__().

同时我们也可以通过以下方法来验证
Python 2.6代码 

class A: 
  pass 

>>type(A) 
<type 'classobj'>

Python 2.6代码 

class A(object): 
  pass 

>>type(A) 
<type 'type'>

从返回的<type 'classobj'>,<type 'type'>可以看出<type 'type'>是我们需要的object类型(python 3.0 将object类作为默认基类,所以都将返回<type 'type'>)

为了考虑代码的python 版本过渡期的兼容性问题,我觉得应该定义class文件的时候,都应该显式定义object,做为一个好习惯

最后的代码将如下:

class Parrot(object): 
  def __init__(self): 
    self._voltage = 100000 

  @property 
  def voltage(self): 
    """Get the current voltage.""" 
    return self._voltage 

  @voltage.setter 
  def voltage(self, new_value): 
    self._voltage = new_value 
 
if __name__ == "__main__": 
  # instance 
  p = Parrot() 
  # similarly invoke "getter" via @property 
  print p.voltage 
  # update, similarly invoke "setter" 
  p.voltage = 12

另外,@property是在2.6、3.0新增的,2.5没有该功能。

Python 相关文章推荐
python通过装饰器检查函数参数数据类型的方法
Mar 13 Python
python引用DLL文件的方法
May 11 Python
Pycharm学习教程(2) 代码风格
May 02 Python
Python使用修饰器执行函数的参数检查功能示例
Sep 26 Python
使用python为mysql实现restful接口
Jan 05 Python
Sublime开发python程序的示例代码
Jan 24 Python
浅谈python numpy中nonzero()的用法
Apr 02 Python
python numpy 按行归一化的实例
Jan 21 Python
python selenium执行所有测试用例并生成报告的方法
Feb 13 Python
Python编译成.so文件进行加密后调用的实现
Dec 23 Python
Python面向对象程序设计之继承、多态原理与用法详解
Mar 23 Python
Python图片处理模块PIL操作方法(pillow)
Apr 07 Python
Python实现类似jQuery使用中的链式调用的示例
Jun 16 #Python
浅析Python中else语句块的使用技巧
Jun 16 #Python
python基础教程之分支、循环简单用法
Jun 16 #Python
python3音乐播放器简单实现代码
Apr 20 #Python
使用python3.5仿微软记事本notepad
Jun 15 #Python
python3.5仿微软计算器程序
Mar 30 #Python
Python的Asyncore异步Socket模块及实现端口转发的例子
Jun 14 #Python
You might like
PHP动态图像的创建
2006/10/09 PHP
台湾中原大学php教程孙仲岳主讲
2008/01/07 PHP
php设计模式 Facade(外观模式)
2011/06/26 PHP
无需重新编译php加入ftp扩展的解决方法
2013/02/07 PHP
PHP  实现等比压缩图片尺寸和大小实例代码
2016/10/08 PHP
PHP面向对象之工作单元(实例讲解)
2017/06/26 PHP
PHP+Ajax简单get验证操作示例
2019/03/02 PHP
js身份证验证超强脚本
2008/10/26 Javascript
JavaScript CSS修改学习第二章 样式
2010/02/19 Javascript
JS 进度条效果实现代码整理
2011/05/21 Javascript
javascript重复绑定事件造成的后果说明
2013/03/02 Javascript
javascript使用window.open提示“已经计划系统关机”的原因
2014/08/15 Javascript
jQuery实现的Tab滑动选项卡及图片切换(多种效果)小结
2015/09/14 Javascript
JavaScript实现点击按钮复制指定区域文本(推荐)
2016/11/25 Javascript
利用js获取下拉框中所选的值
2016/12/01 Javascript
AJAX和jQuery动态加载数据的实现方法
2016/12/05 Javascript
ES6新特性之模块Module用法详解
2017/04/01 Javascript
vue子组件使用自定义事件向父组件传递数据
2017/05/27 Javascript
React利用插件和不用插件实现双向绑定的方法详解
2017/07/03 Javascript
jQuery制作全屏宽度固定高度轮播图(实例讲解)
2017/07/08 jQuery
深入理解angular2启动项目步骤
2017/07/15 Javascript
layui多iframe页面控制定时器运行的方法
2019/09/05 Javascript
[00:30]明星选手化身超级英雄!2018DOTA2亚洲邀请赛全明星赛来临!
2018/04/06 DOTA
python中numpy包使用教程之数组和相关操作详解
2017/07/30 Python
深入理解python中函数传递参数是值传递还是引用传递
2017/11/07 Python
Python小游戏之300行代码实现俄罗斯方块
2019/01/04 Python
Django 过滤器汇总及自定义过滤器使用详解
2019/07/19 Python
Python实现手机号自动判断男女性别(实例解析)
2019/12/22 Python
在html页面中取得session中的值的方法
2020/08/11 HTML / CSS
预订奥兰多和佛罗里达州公园门票:FloridaTix
2018/01/03 全球购物
欧洲有机婴儿食品最大的市场:Organic Baby Food(供美国和加拿大)
2018/03/28 全球购物
自行车租赁公司创业计划书
2014/01/28 职场文书
小学教师节活动方案
2014/01/31 职场文书
乡镇总工会学雷锋活动总结
2014/03/01 职场文书
2014年艾滋病防治工作总结
2014/12/10 职场文书
2015年入党积极分子评语
2015/03/26 职场文书