实例讲解Python编程中@property装饰器的用法


Posted in Python onJune 20, 2016

取值和赋值

class Actress():
  def __init__(self):
    self.name = 'TianXin'
    self.age = 5


类Actress中有两个成员变量name和age。在外部对类的成员变量的操作,主要包括取值和赋值。简单的取值操作是x=object.var,简单的赋值操作是object.var=value。

>>> actress = Actress()
>>> actress.name  #取值操作
'TianXin'
>>> actress.age    #取值操作
20
>>> actress.name = 'NoName'   #赋值操作
>>> actress.name
'NoName'

使用 Getter 和 Setter
上述简单的取值和赋值操作,在某些情况下是不能满足要求的。比如,如果要限制Actress的年龄范围,那么只使用上述简单的赋值操作就不能满足要求了。getter和setter实现这样的要求。

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  def getAge(self):
    return self._age

  def setAge(self, age):
    if age > 30:
      raise ValueError
    self._age = age

调用setAge函数可以实现将变量_age的取值范围限制到小于30.

>>> actress = Actress()
>>> actress.setAge(28)
>>> actress.getAge()
28
>>> actress.setAge(35)
ValueError

使用property
property的定义是:
其中,fget是取值函数,fset是赋值函数,fdel是删除函数。使用property也实现上述对成员变量的取值限制。

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  def getAge(self):
    return self._age

  def setAge(self, age):
    if age > 30:
      raise ValueError
    self._age = age 

  age=property(getAge, setAge, None, 'age property')

经过上面的定义后,可以像简单取值和赋值操作一样操作age。比如,

>>> actress = Actress()
>>> actress.age
20
>>> actress.age = 18
>>> actress.age = 55

ValueError

使用@property
使用@property同样可以实现上述类的定义。

class Actress():
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  @property
  def age(self):
    return self._age

  @age.setter
  def age(self, age):
    if age > 30:
      raise ValueError
    self._age = age

使用时的示例:

>>> actress = Actress()
>>> actress.age
20
>>> actress.age = 18
>>> actress.age = 45
ValueError

Python2 和 Python3中使用property的区别
上述property示例在Python3的环境下有效。在Python2中,使用property时,类定义时需要继承object。否则,property的赋值操作不可使用。

Python2下property的正确使用方式:

class Actress(object):      #差别在这里
  def __init__(self):
    self._name = 'TianXin'
    self._age = 20

  @property
  def age(self):
    return self._age

  @age.setter
  def age(self, age):
    if age > 30:
      raise ValueError
    self._age = age 

  def setName(self, name):
    self._name = name

  def getName(self):
    return self._name

  def delName(self):
    print('Goodbye...')
    del self._name

  name = property(getName, setName, delName, 'name property'

)
实例:快速进行代码重构
从前,Python程序员Alice要打算创建一个代表金钱的类。她的第一个实现形式大概是下面这样:
# 以美元为基础货币的Money类的首个版本
class Money:
  def __init__(self, dollars, cents):
    self.dollars = dollars
    self.cents = cents
    # 还有其他一些方法,我们暂时不必理会

这个类后来被打包到一个Python库里,并且慢慢地被许多不同的应用使用。举个例子,另一个团队中的Python程序员Bob是这样使用Money类的:

money = Money(27, 12)
message = "I have {:d} dollars and {:d} cents."
print(message.format(money.dollars, money.cents))
# "I have 27 dollars and 12 cents."
money.dollars += 2
money.cents += 20
print(message.format(money.dollars, money.cents))
# "I have 29 dollars and 32 cents."

这样使用并没有错,但是却出现了代码可维护性的问题。你发现了吗?

几个月或是几年之后。Alice想要重构Money类的内部实现,不再记录美元和美分,而是仅仅记录美分,因为这样做可以让某些操作简单很多。下面是她很可能会作的修改:

# Money类的第二个版本
class Money:
  def __init__(self, dollars, cents):
    self.total_cents = dollars * 100 + cents

这一修改带来一个后果:引用Money类的每一行代码都必须要调整。有时候很幸运,你就是所有这些代码的维护者,只需要自己直接重构即可。但是Alice的情况就没有这么好了;许多团队都复用了她的代码。因此,她需要协调他们的代码库与自己的修改保持一致,也许甚至要经历一段特别痛苦、漫长的正式弃用过程(deprecation process)。

幸运的是,Alice知道一种更好的解决办法,可以避免这个令人头疼的局面出现:使用Python内建的property装饰器。@property一般应用在Python方法上,可以有效地将属性访问(attribute access)变成方法调用(method call)。举个例子,暂时将Money类抛至一边,假设有一个代表人类的Person类(class):

class Person:
  def __init__(self, first, last):
    self.first = first
    self.last = last
  @property
  def full_name(self):
    return '{} {}'.format(self.first, self.last)

代码样式不同,是因为之前用的工具出问题了。—EarlGrey

请注意full_name方法。除了在def语句上方装饰了@property之外,该方法的声明没有什么不同的地方。但是,这却改变了Person对象的运作方式:

>>> buddy = Person('Jonathan', 'Doe')
>>> buddy.full_name
'Jonathan Doe'

我们发现,尽管full_name被定义为一个方法,但却可以通过变量属性的方式访问。在最后一行代码中没有()操作符;我并没有调用full_name方法。我们所做的,可以说是创建了某种动态属性。

回到本文中的Money类,Alice对它作了如下修改:

# Money类的最终版本
class Money:
  def __init__(self, dollars, cents):
    self.total_cents = dollars * 100 + cents
  # Getter and setter for dollars...
  @property
  def dollars(self):
    return self.total_cents // 100;
  @dollars.setter
  def dollars(self, new_dollars):
    self.total_cents = 100 * new_dollars + self.cents
    # And the getter and setter for cents.
  @property
  def cents(self):
    return self.total_cents % 100;
  @cents.setter
  def cents(self, new_cents):
    self.total_cents = 100 * self.dollars + new_cents

除了使用@property装饰器定义了dollars属性的getter外,Alice还利用@dollars.setter创建了一个setter。Alice还对cents`属性作了类似处理。

那么现在,Bob的代码要做哪些相应的修改呢?根本不用改!

# 他的代码完全没有变动,但是却可以正常调用Money类。
money = Money(27, 12)
message = "I have {:d} dollars and {:d} cents."
print(message.format(money.dollars, money.cents))
# "I have 27 dollars and 12 cents."
money.dollars += 2
money.cents += 20
print(message.format(money.dollars, money.cents))
# "I have 29 dollars and 32 cents."# 代码逻辑也没有问题。
money.cents += 112
print(message.format(money.dollars, money.cents))
# "I have 30 dollars and 44 cents."

事实上,所有使用了Money类的代码都不需要进行修改。Bob不知道或根本不在乎Alice去除了类中的dollars和cents属性:他的代码还是和以前一样正常执行。唯一修改过的代码就是Money类本身。

正是由于Python中处理装饰器的方式,你可以在类中自由使用简单的属性。如果你所写的类改变了管理状态的方法,你可以自信地通过@property装饰器对这个类(且只有这个类)进行修改。这是一个共赢的方法!相反,在Java等语言中,程序员必须主动去定义访问属性的方法(例如getDollars或setCents)。

最后要提示大家:这种方法对于那些被其他程序员和团队复用的代码最为重要。假设仅仅是在你自己一个维护的应用中创建一个类似Money的类,那么如果你改变了Money的接口,你只需要重构自己的代码就可以。这种情况下,你没有必要像上面说的那样使用@property装饰器。

Python 相关文章推荐
在win和Linux系统中python命令行运行的不同
Jul 03 Python
解决Python requests 报错方法集锦
Mar 19 Python
Python中关于Sequence切片的下标问题详解
Jun 15 Python
python使用pil库实现图片合成实例代码
Jan 20 Python
Windows环境下python环境安装使用图文教程
Mar 13 Python
Django objects的查询结果转化为json的三种方式的方法
Nov 07 Python
Python Collatz序列实现过程解析
Oct 12 Python
基于Python+Appium实现京东双十一自动领金币功能
Oct 31 Python
详解Python设计模式之策略模式
Jun 15 Python
如何在vscode中安装python库的方法步骤
Jan 06 Python
Python实现视频中添加音频工具详解
Dec 06 Python
python的netCDF4批量处理NC格式文件的操作方法
Mar 21 Python
Python的包管理器pip更换软件源的方法详解
Jun 20 #Python
python3.5使用tkinter制作记事本
Jun 20 #Python
浅谈python抛出异常、自定义异常, 传递异常
Jun 20 #Python
python3 与python2 异常处理的区别与联系
Jun 19 #Python
浅谈Python的异常处理
Jun 19 #Python
qpython3 读取安卓lastpass Cookies
Jun 19 #Python
python3实现读取chrome浏览器cookie
Jun 19 #Python
You might like
全国FM电台频率大全 - 9 上海市
2020/03/11 无线电
PHP数组与对象之间使用递归实现转换的方法
2015/06/24 PHP
php实现图片上传并利用ImageMagick生成缩略图
2016/03/14 PHP
TP5框架使用QueryList采集框架爬小说操作示例
2020/03/26 PHP
JavaScript 实现??打印?理
2007/04/28 Javascript
javascript常用方法、属性集合及NodeList 和 HTMLCollection 的浏览器差异
2010/12/25 Javascript
JS完成代码前最好对其做5件事
2013/04/07 Javascript
浅析JS原型继承与类的继承
2016/04/07 Javascript
关于javascript原型的修改与重写(覆盖)差别详解
2016/08/31 Javascript
基于JavaScript实现随机颜色输入框
2016/12/10 Javascript
以BootStrap Tab为例写一个前端组件
2017/07/25 Javascript
JavaScript的级联函数用法简单示例【链式调用】
2019/03/26 Javascript
Vue2.0使用嵌套路由实现页面内容切换/公用一级菜单控制页面内容切换(推荐)
2019/05/08 Javascript
JavaScript中的ES6 Proxy的具体使用
2019/06/16 Javascript
layui中的switch开关实现方法
2019/09/03 Javascript
原生javascript制作贪吃蛇小游戏的方法分析
2020/02/26 Javascript
小程序开发之模态框组件封装
2020/04/23 Javascript
Vue 如何使用props、emit实现自定义双向绑定的实现
2020/06/05 Javascript
使用Vant完成通知栏Notify的提示操作
2020/11/11 Javascript
[04:21]狐狸妈带你到现场 DOTA2 TI中国区预选赛线下赛路线指引
2014/05/22 DOTA
Python中字符串的处理技巧分享
2016/09/17 Python
python 删除非空文件夹的实例
2018/04/26 Python
django的settings中设置中文支持的实现
2019/04/28 Python
python机器学习实现决策树
2019/11/11 Python
Python3.7在anaconda里面使用IDLE编译器的步骤详解
2020/04/29 Python
基于python调用jenkins-cli实现快速发布
2020/08/14 Python
python3列表删除大量重复元素remove()方法的问题详解
2021/01/04 Python
新闻网站实习自我鉴定
2013/09/25 职场文书
毕业实习个人鉴定范文
2013/12/10 职场文书
学期研究性学习个人的自我评价
2014/01/09 职场文书
国贸专业自荐信范文
2014/03/02 职场文书
促销活动总结报告
2014/04/26 职场文书
法人授权委托书范本
2014/09/17 职场文书
2014财务部年度工作总结
2014/12/08 职场文书
刑事起诉书范文
2015/05/19 职场文书
幼儿园教师读书笔记
2015/06/29 职场文书