上一章Python教程请查看:python3装饰器decorator
你将了解Python 的@property,python使用getter和setter的方法。
Python有一个非常好的概念叫做property,它使面向对象程序员的生活变得更加简单。
在定义和深入了解@property是什么之前,让我们首先建立一个关于为什么需要它的直觉。
@property第一个例子
假设你决定创建一个以摄氏度为单位存储温度的类,还将实现一种将温度转换为华氏温度的方法,其中一种方法如下。
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def 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._temperature
def 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 = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
def get_temperature(self):
print("获取值")
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("零下273度是不可能的")
print("设置值")
self._temperature = value
temperature = 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 = temperature
def 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中查找属性时,你很可能会遇到这些类型的构造。
评论前必须登录!
注册