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

取值和赋值

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装饰器。

时间: 2016-06-17

Python编程中装饰器的使用示例解析

装饰函数和方法 我们先定义两个简单的数学函数,一个用来计算平方和,一个用来计算平方差: # get square sum def square_sum(a, b): return a**2 + b**2 # get square diff def square_diff(a, b): return a**2 - b**2 print(square_sum(3, 4)) print(square_diff(3, 4)) 在拥有了基本的数学功能之后,我们可能想为函数增加其它的功能,比如打印输入.我们

python装饰器初探(推荐)

一.含有一个装饰器 #encoding: utf-8 ############含有一个装饰器######### def outer(func): def inner(*args, **kwargs):#要装饰f1(),这里用这俩形式参数,可以接受任意个参数,不管f1定义几个参数 print "1" r = func(*args, **kwargs)#这里要用func,不要用f1 print "2" return r return inner @outer #这里ou

Python的装饰器用法学习笔记

在python中常看到在定义函数是使用@func. 这就是装饰器, 装饰器是把一个函数作为参数的函数,常常用于扩展已有函数,即不改变当前函数状态下增加功能. def run(): print "I'm run." 我有这么一个函数, 我想知道这个函数什么时候开始什么时候结束. 我应该这么写 def run(): print time.ctime() print "I'm run." print time.ctime() 但是如果不允许修改函数的话就需要装饰器了 de

python中函数总结之装饰器闭包详解

1.前言 函数也是一个对象,从而可以增加属性,使用句点来表示属性. 如果内部函数的定义包含了在外部函数中定义的对象的引用(外部对象可以是在外部函数之外),那么内部函数被称之为闭包. 2.装饰器 装饰器就是包装原来的函数,从而在不需要修改原来代码的基础之上,可以做更多的事情. 装饰器语法如下: @deco2 @deco1 def func(arg1,arg2...): pass 这个表示了有两个装饰器的函数,那么表示的含义为:func = deco2(deco1(func)) 无参装饰器语法如下:

深入理解Python中装饰器的用法

因为函数或类都是对象,它们也能被四处传递.它们又是可变对象,可以被更改.在函数或类对象创建后但绑定到名字前更改之的行为为装饰(decorator). "装饰器"后隐藏了两种意思--一是函数起了装饰作用,例如,执行真正的工作,另一个是依附于装饰器语法的表达式,例如,at符号和装饰函数的名称. 函数可以通过函数装饰器语法装饰: @decorator # ② def function(): # ① pass 函数以标准方式定义.① 以@做为定义为装饰器函数前缀的表达式②.在 @ 后的部分必须

深入学习Python中的装饰器使用

装饰器 vs 装饰器模式 首先,大家需要明白的是使用装饰器这个词可能会有不少让大家担忧的地方,因为它很容易和设计模式这本书里面的装饰器模式发生混淆.曾经一度考虑给这个新的功能取一些其它的术语名称,但是装饰器最终还是胜出了. 的确,你可以使用python装饰器来实现装饰器模式,但这绝对是它很小的一部分功能,有点暴殄天物.对于python装饰器,我觉得它是最接近宏的存在. 宏的历史 宏有有着非常悠久的历史,不过大多数人可能会有使用C语言预处理宏的经验.但是,对于C语言里的宏来说,它存在一些问题,(1

Python中使用装饰器来优化尾递归的示例

尾递归简介 尾递归是函数返回最后一个操作是递归调用,则该函数是尾递归. 递归是线性的比如factorial函数每一次调用都会创建一个新的栈(last-in-first-out)通过不断的压栈,来创建递归, 很容易导致栈的溢出.而尾递归则使用当前栈通过数据覆盖来优化递归函数. 阶乘函数factorial, 通过把计算值传递的方法完成了尾递归.但是python不支出编译器优化尾递归所以当递归多次的话还是会报错(学习用). eg: def factorial(n, x): if n == 0: ret

详解python中的装饰器

在了解装饰器之前,我们需要知道什么闭包是什么鬼! 闭包:在一个函数内定义了一个函数f,并且这个函数f引用外部变量,在把这个函数f当做返回值返回. 上述说了闭包的三个条件: 1 函数内定义了一个函数f 2 f函数引用了外部变量 3 f被当做返回值返回 def t1():#定义t1函数 x=2 def f():#t1函数内部定义了f函数 print(x)#f函数引用了不属于自己内部的变量x return f #f被当做返回值返回 而装饰器有是什么鬼呢?其实闭包的一种运用. 装饰器:在不改变函数(当然

Python中的装饰器用法详解

本文实例讲述了Python中的装饰器用法.分享给大家供大家参考.具体分析如下: 这里还是先由stackoverflow上面的一个问题引起吧,如果使用如下的代码: 复制代码 代码如下: @makebold @makeitalic def say():    return "Hello" 打印出如下的输出: <b><i>Hello<i></b> 你会怎么做?最后给出的答案是: 复制代码 代码如下: def makebold(fn):    

详解Python中的装饰器、闭包和functools的教程

装饰器(Decorators) 装饰器是这样一种设计模式:如果一个类希望添加其他类的一些功能,而不希望通过继承或是直接修改源代码实现,那么可以使用装饰器模式.简单来说Python中的装饰器就是指某些函数或其他可调用对象,以函数或类作为可选输入参数,然后返回函数或类的形式.通过这个在Python2.6版本中被新加入的特性可以用来实现装饰器设计模式. 顺便提一句,在继续阅读之前,如果你对Python中的闭包(Closure)概念不清楚,请查看本文结尾后的附录,如果没有闭包的相关概念,很难恰当的理解P

快速了解Python中的装饰器

需要理解的一些概念 要理解Python中的装饰器,我觉得还是应该从最基本的概念开始: 装饰器模式:所谓的装饰器模式,可以简单地理解为"在不改变原有内部实现的情况下,为函数或者类添加某种特性".这样我们就可以将一些与业务无关.具有通用性的代码抽象出来,作为装饰器附加到需要这些代码的函数或者类之上.用面向切面编程的思想解释就是"装饰器应该是一个切面". 函数是一等公民:意思就是函数可以被当成普通变量一样使用.在Python中,可以把函数赋值给变量,可以将函数作为其它函数

简单理解Python中的装饰器

Python的装饰器可以实现在代码运行期间修改函数的上下文, 即可以定义函数在执行之前进行何种操作和函数执行后进行何种操作, 而函数本身并没有任何的改变. 首先, 我们先定义一个函数, 这个函数可以输出我的个人昵称: def my_name(): print "Yi_Zhi_Yu" my_name() # Yi_Zhi_Yu 那假如我需要在个人昵称输出前, 在输出我的个人uid呢, 当然, 要求是不改动现有的my_name函数, 这个时候就可以使用装饰器了 首先, 装饰器也是个函数,

Python中使用装饰器时需要注意的一些问题

装饰器基本概念 大家都知道装饰器是一个很著名的设计模式,经常被用于AOP(面向切面编程)的场景,较为经典的有插入日志,性能测试,事务处理,Web权限校验,Cache等. Python语言本身提供了装饰器语法(@),典型的装饰器实现如下: @function_wrapper def function(): pass @实际上是python2.4才提出的语法糖,针对python2.4以前的版本有另一种等价的实现: def function(): pass function = function_wr

Python中使用装饰器和元编程实现结构体类实例

Ruby中有一个很方便的Struct类,用来实现结构体.这样就不用费力的去定义一个完整的类来仅仅用作访问属性. 复制代码 代码如下: class Dog < Struct.new(:name, :age) end fred = Dog.new("fred", 5) printf "name:%s age:%d", fred.name, fred.age ##name:fred age:5 Python3.4中也可以这么干,但写法很累赘.其中包含self.nam

简单说明Python中的装饰器的用法

装饰器对与Python新手以至于熟悉Python的人都是一个难理解, 难写的东西. 那么今天就分享一下我对Python 装饰器的理解 所谓装饰器仅仅是一种语法糖, 可作用的对象可以是函数也可以是类, 装饰器本身是一个函数, 其主要工作方式就是将被装饰的类或者函数当作参数传递给装饰器函数, 比如定义如下装饰器 import time def run_time(func): def wrapper(*args, **kwargs): start = time.time() r = func(*arg