实例讲解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操作MongoDB基础知识
Nov 01 Python
Python的Django框架中URLconf相关的一些技巧整理
Jul 18 Python
图文详解WinPE下安装Python
May 17 Python
Python中判断输入是否为数字的实现代码
May 26 Python
Python基础教程之异常详解
Jan 10 Python
python的内存管理和垃圾回收机制详解
May 18 Python
Python配置文件处理的方法教程
Aug 29 Python
python GUI库图形界面开发之PyQt5窗口背景与不规则窗口实例
Feb 25 Python
python json.dumps中文乱码问题解决
Apr 01 Python
python爬虫学习笔记之Beautifulsoup模块用法详解
Apr 09 Python
PyQt5实现登录页面
May 30 Python
pytorch MSELoss计算平均的实现方法
May 12 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
php curl中gzip的压缩性能测试实例分析
2016/11/08 PHP
PHP中引用类型和值类型功能与用法示例
2019/02/26 PHP
JavaScript Base64编码和解码,实现URL参数传递。
2006/09/18 Javascript
javascript全局变量封装模块实现代码
2012/11/28 Javascript
jQuery右下角旋转环状菜单特效代码
2015/08/10 Javascript
AngularJs IE Compatibility 兼容老版本IE
2016/09/01 Javascript
js实现音频控制进度条功能
2017/04/01 Javascript
Javascript实现时间倒计时效果
2017/07/15 Javascript
Node.js学习教程之HTTP/2服务器推送【译】
2017/10/31 Javascript
Angular实现的内置过滤器orderBy排序与模糊查询功能示例
2017/12/29 Javascript
解决webpack无法通过IP地址访问localhost的问题
2018/02/22 Javascript
一种angular的方法级的缓存注解(装饰器)
2018/03/13 Javascript
vue watch深度监听对象实现数据联动效果
2018/08/16 Javascript
Node.js Stream ondata触发时机与顺序的探索
2019/03/08 Javascript
使用taro开发微信小程序遇到的坑总结
2019/04/08 Javascript
Electron + vue 打包桌面操作流程详解
2019/06/24 Javascript
[04:46]2018年度玩家喜爱的电竞媒体-完美盛典
2018/12/16 DOTA
Python的内存泄漏及gc模块的使用分析
2014/07/16 Python
深入浅出学习python装饰器
2017/09/29 Python
django实现同一个ip十分钟内只能注册一次的实例
2017/11/03 Python
Python wxPython库消息对话框MessageDialog用法示例
2018/09/03 Python
CentOS下Python3的安装及创建虚拟环境的方法
2018/11/28 Python
python 已知平行四边形三个点,求第四个点的案例
2020/04/12 Python
Python爬虫破解登陆哔哩哔哩的方法
2020/11/17 Python
佳能加拿大网上商店:Canon eStore Canada
2018/04/04 全球购物
瑞士最大的图书贸易公司:Orell Füssli
2019/12/28 全球购物
美国优质宠物用品购买网站:Muttropolis
2020/02/17 全球购物
Vilebrequin美国官方网上商店:法国豪华泳装品牌
2020/02/22 全球购物
前台文员岗位职责
2013/12/28 职场文书
接待员岗位责任制
2014/02/10 职场文书
安卓程序员求职信
2014/02/28 职场文书
家居装修公司创业计划书范文
2014/03/20 职场文书
保密协议书范本
2014/04/22 职场文书
征兵宣传标语
2014/06/20 职场文书
python如何利用cv2模块读取显示保存图片
2021/06/04 Python
Redis全局ID生成器的实现
2022/06/05 Redis