python3 @property的用法 – Python3教程

上一章Python教程请查看:python3装饰器decorator
【python3 @property的用法 – Python3教程】你将了解Python 的@property,python使用getter和setter的方法。
Python有一个非常好的概念叫做property,它使面向对象程序员的生活变得更加简单。
在定义和深入了解@property是什么之前,让我们首先建立一个关于为什么需要它的直觉。
@property第一个例子假设你决定创建一个以摄氏度为单位存储温度的类,还将实现一种将温度转换为华氏温度的方法,其中一种方法如下。

class Celsius: def __init__(self, temperature = 0): self.temperature = temperaturedef to_fahrenheit(self): return (self.temperature * 1.8) + 32

我们可以从这个类中创建对象,并按自己的意愿操作属性temperature,在Python shell上试试这些。
>>> # 创建一个新对象 >>> man = Celsius() >>> # 设置temperature >>> man.temperature = 37 >>> # 获取temperature >>> man.temperature 37 >>> # 得到华氏度 >>> man.to_fahrenheit() 98.60000000000001

在转换为华氏温度时,额外的小数位数是由于浮点算术错误(在Python解释器中尝试1.1 + 2.2)造成的。
当我们分配或检索任何对象属性(如上面所示)时,Python会在对象的__dict__字典中搜索它。
>>> man.__dict__ {'temperature': 37}

因此,man.temperature在内部变成了man.__dict__[‘ temperature’ ]。
现在,让我们进一步假设我们的类在客户中很受欢迎,并且他们开始在他们的程序中使用它,他们对对象做了各种各样的赋值。
有一天,一个值得信赖的客户来找我们,建议温度不能低于-273摄氏度(热力学专业的学生可能会说实际上是-273.15摄氏度),也叫绝对零度。他进一步要求我们实现这个值约束,作为一家追求客户满意度的公司,我们很高兴地听取了这个建议,并发布了1.01版本(对现有类的升级)。
使用getter和setter对上述约束的一个明显的解决方案是隐藏属性temperature(使其成为私有),并定义新的getter和setter接口来操作它,可以这样做。
class Celsius: def __init__(self, temperature = 0): self.set_temperature(temperature)def to_fahrenheit(self): return (self.get_temperature() * 1.8) + 32# new update def get_temperature(self): return self._temperaturedef set_temperature(self, value): if value < -273: raise ValueError("零下273度是不可能的") self._temperature = value

我们可以看到,上面定义了新的方法get_temperature()和set_temperature(),而且还用_temperature代替了temperature。在Python中,开头的下划线(_)用于表示私有变量。
>>> c = Celsius(-277) Traceback (most recent call last): ... ValueError: 零下273度是不可能的 >>> c = Celsius(37) >>> c.get_temperature() 37 >>> c.set_temperature(10) >>> c.set_temperature(-300) Traceback (most recent call last): ... ValueError: 零下273度是不可能的

这次更新成功地实现了新的限制,我们不再被允许将温度设置在-273以下。
请注意,在Python中不存在私有变量,有一些简单的准则可以遵循,语言本身没有任何限制。
>>> c._temperature = -300 >>> c.get_temperature() -300

但这不是很大的问题。 上述更新的最大问题在于,所有在程序中实现了上一类的客户端都必须将其代码从obj.temperature修改为obj.get_temperature(),并将所有分配(如obj.temperature = val修改为obj.set_temperature( val)。
这种重构可能会给客户带来数十万行代码的麻烦。
总而言之,我们的新更新不向后兼容,这就是使用@property的地方了。
@ property的功能处理上述问题的python方法是使用属性,这是我们可以如何实现它。
class Celsius: def __init__(self, temperature = 0): self.temperature = temperaturedef to_fahrenheit(self): return (self.temperature * 1.8) + 32def get_temperature(self): print("获取值") return self._temperaturedef set_temperature(self, value): if value < -273: raise ValueError("零下273度是不可能的") print("设置值") self._temperature = valuetemperature = property(get_temperature,set_temperature)

然后,在shell中运行以下代码。
>>> c = Celsius()

我们在get_temperature()和set_temperature()中添加了print()函数,以便清楚地观察它们的执行情况。
代码的最后一行,表示属性对象的温度。简单地说,property将一些代码(get_temperature和set_temperature)附加到成员属性访问(temperature)。
检索温度值的任何代码都将自动调用get_temperature(),而不是字典查找,类似地,任何为temperature赋值的代码都会自动调用set_temperature(),这是Python中一个很酷的特性。
我们可以看到,即使在创建对象时也调用了set_temperature()。
@property是如何工作的?原因是创建对象时,将调用__init __()方法,此方法有一行代码为self.temperature = temperature,此分配自动为set_temperature()。
>>> c.temperature 获取值 0

同样,任何诸如c.temperature之类的访问都将自动调用get_temperature(),这就是@property的作用,这里还有一些例子。。
>>> c.temperature = 37 设置值 >>> c.to_fahrenheit() 获取值 98.60000000000001

通过使用属性,我们可以看到,我们修改了类并实现了值约束,而不需要对客户机代码进行任何更改。因此,我们的实现是向后兼容的,每个人都很高兴。
最后请注意,实际的温度值存储在私有变量_temperature中,属性temperature是一个属性对象,它提供了这个私有变量的接口。
深入理解@property在Python中,property()是一个内置函数,它创建并返回一个property对象。这个函数的特征是
property(fget=None, fset=None, fdel=None, doc=None)

其中,fget是获取属性值的函数,fset是设置属性值的函数,fdel是删除属性的函数,doc是字符串(如注释)。从实现中可以看出,这些函数参数是可选的,因此,可以简单地按照以下方式创建属性对象。
>>> property() < property object at 0x0000000003239B38>

属性对象有三个方法,getter()、setter()和deleter(),用于稍后指定fget、fset和fdel,这意味着,下面的代码:
temperature = property(get_temperature,set_temperature)

可以分解成
temperature = property() # 分配fget temperature = temperature.getter(get_temperature) # 分配fset temperature = temperature.setter(set_temperature)

这两段代码是等价的。
熟悉Python中decorator的程序员可以认识到,上面的结构可以作为decorator实现。
我们可以更进一步,不定义名称get_temperature和set_temperature,因为它们是不必要的,并且会影响类命名空间。为此,我们在定义getter和setter函数时重用了名称temperature,这是可以做到的。
class Celsius: def __init__(self, temperature = 0): self._temperature = temperaturedef to_fahrenheit(self): return (self.temperature * 1.8) + 32@property def temperature(self): print("获取值") return self._temperature@temperature.setter def temperature(self, value): if value < -273: raise ValueError("零下273度是不可能的") print("设置值") self._temperature = value

上面的实现是创建属性的一种简单而推荐的方法,在Python中查找属性时,你很可能会遇到这些类型的构造。

    推荐阅读