实例讲解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 相关文章推荐
python使用cPickle模块序列化实例
Sep 25 Python
Python 字符串大小写转换的简单实例
Jan 21 Python
Python之os操作方法(详解)
Jun 15 Python
python快速建立超简单的web服务器的实现方法
Feb 17 Python
Python安装与基本数据类型教程详解
May 29 Python
Django 自动生成api接口文档教程
Nov 19 Python
python二元表达式用法
Dec 04 Python
详解Python中namedtuple的使用
Apr 27 Python
pycharm设置默认的UTF-8编码模式的方法详解
Jun 01 Python
python学习将数据写入文件并保存方法
Jun 07 Python
Python调用OpenCV实现图像平滑代码实例
Jun 19 Python
Golang Web 框架Iris安装部署
Aug 14 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电台频率大全 - 28 甘肃省
2020/03/11 无线电
Classes and Objects in PHP5-面向对象编程 [1]
2006/10/09 PHP
分享下页面关键字抓取www.icbase.com站点代码(带asp.net参数的)
2014/01/30 PHP
浅析PHP的静态成员函数效率更高的原因
2014/06/13 PHP
基于CakePHP实现的简单博客系统实例
2015/06/28 PHP
PHP获取指定月份第一天和最后一天的方法
2015/07/18 PHP
PHP获取某个月最大天数(最后一天)的方法
2015/07/29 PHP
PHP中file_put_contents追加和换行的实现方法
2017/04/01 PHP
PHP大文件及断点续传下载实现代码
2020/08/18 PHP
html读出文本文件内容
2007/01/22 Javascript
JavaScript XML和string相互转化实现代码
2011/07/04 Javascript
基于jQuery的倒计时实现代码
2012/05/30 Javascript
JavaScript获取onclick、onchange等事件值的代码
2013/07/22 Javascript
javascript Deferred和递归次数限制实例
2014/10/21 Javascript
jQuery数据检索中根据关键字快速定位GridView指定行的实现方法
2016/06/08 Javascript
基于BootStrap实现局部刷新分页实例代码
2016/08/08 Javascript
轮播图组件js代码
2016/08/08 Javascript
Bootstrap jquery.twbsPagination.js动态页码分页实例代码
2017/02/20 Javascript
原生JS实现幻灯片
2017/02/22 Javascript
Javascript实现base64的加密解密方法示例
2017/06/27 Javascript
微信小程序 同步请求授权的详解
2017/08/04 Javascript
详解webpack + vue + node 打造单页面(入门篇)
2017/09/23 Javascript
解决js ajax同步请求造成浏览器假死的问题
2018/01/18 Javascript
详谈vue+webpack解决css引用图片打包后找不到资源文件的问题
2018/03/06 Javascript
vue实现点击当前标签高亮效果【推荐】
2018/06/22 Javascript
vue里面使用mui的弹出日期选择插件实例
2018/09/16 Javascript
vue微信分享出来的链接点开是首页问题的解决方法
2018/11/28 Javascript
layui radio单选限制下一个radio单选的实例
2019/09/03 Javascript
请求时token过期自动刷新token操作
2020/09/11 Javascript
python实现寻找最长回文子序列的方法
2018/06/02 Python
JSON文件及Python对JSON文件的读写操作
2018/10/07 Python
html5时钟实现代码
2010/10/22 HTML / CSS
《纸船和风筝》教学反思
2014/02/15 职场文书
合伙经营协议书范本(通用版)
2014/12/03 职场文书
2015年行政人事部工作总结
2015/05/13 职场文书
5个pandas调用函数的方法让数据处理更加灵活自如
2022/04/24 Python