Python @property 的用法

转载自:http://python.jobbole.com/81967/

Python中有个很赞的概念,叫做property,它使得面向对象的编程更加简单。在详细解释和深入了解Python中的property之前,让我们首先建立这样一个直觉:为什么我们需要用到property?

从一个实例开始

假设有天你决定创建一个类,用来存储摄氏温度。当然这个类也需要实现一个将摄氏温度转换为华氏温度的方法。一种实现的方式如下:

我们可以用这个类产生一个对象,然后按照我们期望的方式改变该对象的温度属性:

这里额外的小数部分是转换成华氏温度时由于浮点运算误差造成的(你可以在Python解释器中试试1.1 + 2.2)。每当我们赋值或获取任何对象的属性时,例如上面展示的温度,Python都会从对象的__dict__字典中搜索它。

因此,man.temperature在其内部就变成了man.__dict__['temperature']

现在,让我们进一步假设我们的类在客户中很受欢迎,他们开始在其程序中使用这个类。他们对该类生成的对象做了各种操作。有一天,一个受信任的客户来找我们,建议温度不能低于-273摄氏度(热力学的同学可能会提出异议,它实际上是-273.15),也被称为绝对零。客户进一步要求我们实现这个值约束。作为一个以争取客户满意度为己任的公司,我们很高兴地听从了建议,发布了1.01版本,升级了我们现有的类。

使用Getters和Setters

对于上边的约束,一个很容易想到的解决方案是隐藏其温度属性(使其私有化),并且定义新的用于操作温度属性的getter和setter接口。可以这么实现:

从上边可以看出,我们定义了两个新方法get_temperature()set_temperature(),此外属性temperature也被替换为了_temperature。最前边的下划线(_)用于指示Python中的私有变量。

这个更新成功地实现了新约束,我们不再允许设置温度低于-273度。

请注意,Python中实际上是没有私有变量的。有一些简单的被遵循的规范。Python本身不会应用任何限制。

但这样并不会让人很放心。上述更新的最大问题是,所有在他们的程序中使用了我们先前类的客户都必须更改他们的代码:obj.temperature改为obj.get_temperature(),所有的赋值语句也必须更改,比如obj.temperature = val改为obj.set_temperature(val)。这样的重构会给那些拥有成千上万行代码的客户带来很大的麻烦。

总而言之,我们的更新是不向后兼容地。这就是需要property闪亮登场的地方。

Property的作用

对于上边的问题,Python式的解决方式是使用property。这里是我们已经实现了的一个版本:

我们在get_temperature()set_temperature()的内部增加了一个print()函数,用来清楚地观察它们是否正在执行。代码的最后一行,创建了一个property对象temperature。简单地说,property将一些代码(get_temperatureset_temperature)附加到成员属性(temperature)的访问入口。任何获取temperature值的代码都会自动调用get_temperature(),而不是去字典表(__dict__)中进行查找。同样的,任何赋给temperature值的代码也会自动调用set_temperature()。这是Python中一个很酷的功能。我们实际演示一下。

从上边的代码中我们可以看到,即使当我们创建一个对象时,set_temperature()也会被调用。你能猜到为什么吗?原因是,当一个对象被创建时,__init__()方法被调用。该方法有一行代码self.temperature = temperature。这个任务会自动调用set_temperature()方法。

同样的,对于属性的任何访问,例如c.temperature,也会自动调用get_temperature()方法。这就是property所作的事情。这里有一些额外的实例。

我们可以看到,通过使用property,我们在不需要客户代码做任何修改的情况下,修改了我们的类,并实现了值约束。因此我们的实现是向后兼容的,这样的结果,大家都很高兴。

最后需要注意的是,实际温度值存储在私有变量_temperature中。属性temperature是一个property对象,是用来为这个私有变量提供接口的。

深入挖掘property

在Python中,property()是一个内置函数,用于创建和返回一个property对象。该函数的签名为:

这里,fget是一个获取属性值的函数,fset是一个设置属性值的函数,fdel是一个删除属性的函数,doc是一个字符串(类似于注释)。从函数实现上看,这些函数参数都是可选的。所以,可以按照如下的方式简单的创建一个property对象。

Property对象有三个方法,getter(), setter()和delete(),用来在对象创建后设置fget,fset和fdel。这就意味着,这行代码:temperature = property(get_temperature,set_temperature)可以被分解为:

它们之间是相互等价的。

熟悉Python中装饰器decorator的程序员能够认识到上述结构可以作为decorator实现。我们可以更进一步,不去定义名字get_temperature和set_temperature,因为他们不是必须的,并且污染类的命名空间。为此,我们在定义getter函数和setter函数时重用名字temperature。下边的代码展示如何实现它。

上边的两种生成property的实现方式,都很简单,推荐使用。在Python寻找property时,你很可能会遇到这种类似的代码结构。

[D]python编程property()用法

07-01

python 2.5版本:rnrn[code=Python]rn#这个版本无法正确运行rn__metaclass__ = typernrnclass rectangle :rnrn def __init__(self, arg_width=0, arg_height=0) :rn self.width = arg_widthrn self.height = arg_heightrnrn def setItem( self, *value ) : #可接受元祖参数值rn self.width, self.height = valuernrn def getItem(self) :rn return self.width, self.heightrnrn def delItem(self) :rn del self.widthrn del self.heightrnrn size = property(getItem,setItem,delItem, "display rectangle property")rnrn#这个版本可以通过size返回属性,但不能通过size设置属性:rn>>> rect = rectangle()rn>>> rect.width , rect.height = 10, 20rn>>> rect.sizern(10, 20)rn>>> rect.size(100,200) #通过size设置width ,height时rnrnTraceback (most recent call last):rn File "", line 1, in rn rect.size(100,200)rnTypeError: 'tuple' object is not callablern>>> rect.size = 100, 200 #通过size设置width ,height时rnrnTraceback (most recent call last):rn File "", line 1, in rn rect.size = 100, 200rn File "C:/Users/TOSHIBA/Desktop/TmpFile/PythonCode/property", line 10, in setItemrn self.width, self.height = valuernValueError: need more than 1 value to unpackrn>>> rect.size = (100,200) #通过size设置width ,height时rnrnTraceback (most recent call last):rn File "", line 1, in rn rect.size = (100,200)rn File "C:/Users/TOSHIBA/Desktop/TmpFile/PythonCode/property", line 10, in setItemrn self.width, self.height = valuernValueError: need more than 1 value to unpackrnrn[/code]rnrn[code=Python]rnrn#这个版本可以正确运行rnrn__metaclass__ = typernrnclass rectangle :rnrn def __init__(self, arg_width=0, arg_height=0) :rn self.width = arg_widthrn self.height = arg_heightrnrn def setItem( self, tuple ) : #显示定义可接受元祖参数rn self.width, self.height = tuplernrn def getItem(self) :rn return self.width, self.heightrnrn def delItem(self) :rn del self.widthrn del self.heightrnrn size = property(getItem,setItem,delItem, "display rectangle property")rnrn[/code]rnrn[code=Python]rn#这个是《python基础教程》(第2版) 上148页的例题,也无法正确运行rn__metaclass__ = typernrnclass rectangle :rnrn def __init__(self) :rn self.width = 0rn self.height = 0rnrn def setItem( self, value ) : rn self.width, self.height = valuernrn size = property(getItem,setItem)rnrn[/code]rnrn官方说明:rnrnproperty( [fget[, fset[, fdel[, doc]]]]) rnrnReturn a property attribute for new-style classes (classes that derive from object). rnfget is a function for getting an attribute value, likewise fset is a function for setting, and fdel a function for del'ing, an attribute. Typical use is to define a managed attribute x: rnrnrn[code=Python]rnclass C(object):rn def __init__(self): self.__x = Nonern def getx(self): return self._xrn def setx(self, value): self._x = valuern def delx(self): del self._xrn x = property(getx, setx, delx, "I'm the 'x' property.")rn[/code]rnrnrnIf given, doc will be the docstring of the property attribute. Otherwise, the property will copy fget's docstring (if it exists). This makes it possible to create read-only properties easily using property() as a decorator: rnrn[code=Python]rnclass Parrot(object):rn def __init__(self):rn self._voltage = 100000rnrn @propertyrn def voltage(self):rn """Get the current voltage."""rn return self._voltagern[/code]rnrnturns the voltage() method into a ``getter'' for a read-only attribute with the same name. rnrnNew in version 2.2. Changed in version 2.5: Use fget's docstring if no doc given. rnrnrn谁给讲解下。thxrnrn---------------------------rnDouble行动:rn原帖分数:20rn帖子加分:20 论坛

没有更多推荐了,返回首页