Python中有个很赞的概念,叫做property,它使得面向对象的编程更加简单。在详细解释和深入了解Python中的property之前,让我们首先建立这样一个直觉:为什么我们需要用到property?
1.实例
创建一个类,用来存放摄氏温度。当然这个类也需要实现一个将摄氏温度转换为华氏温度的方法,我们可以用这个类产生一个对象,然后按照我们期望的方式改变该对象的温度属性。一种实现的方式如下:
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
if "__main__" == __name__:
man = Celsius()
man.temperature = 37
print(man.temperature)
print(man.to_fahrenheit())
每当赋值或取任何对象的属性时,例如上面展示的温度,Python都会从对象的__dict__
字典(内部不包括私有属性)中搜索。因此,man.temperature在其内部就变成了man.__dict__['temperature']
print(man.__dict__) # {'temperature': 37}
现在,让我们进一步假设我们的类在客户中很受欢迎,他们开始在其程序中使用这个类。他们对该类生成的对象做了各种操作。有一天,一个受信任的客户来找我们,建议温度不能低于-273摄氏度(热力学的同学可能会提出异议,它实际上是-273.15),也被称为绝对零。客户进一步要求我们实现这个值约束。作为一个以争取客户满意度为己任的公司,我们很高兴地听从了建议,发布了1.01版本,升级了我们现有的类。
2.使用Getters和Setters
对于上边的约束,一个很容易想到的解决方案是隐藏其温度属性(使其私有化),并且定义新的用于操作温度属性的getter和setter接口。可以如下方式实现。但这样并不会让人很放心。上述更新的最大问题是,所有在他们的程序中使用了我们先前类的客户都必须更改他们的代码:obj.temperature改为obj.get_temperature(),所有的赋值语句也必须更改,比如obj.temperature = val改为obj.set_temperature(val)。这样的重构会给那些拥有成千上万行代码的客户带来很大的麻烦。
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("Temperature below -273 is not possible")
# _temperature为类的私有属性(精确意义上来说python有私有属性)
self._temperature = value
if "__main__" == __name__:
# c = Celsius(-277) # error
c = Celsius(37)
c.get_temperature()
c.set_temperature(10)
# c.set_temperature(-300) # error
3.property的作用
对于上边的问题,Python式的解决方式是使用property。这里是我们已经实现了的一个版本(如下部代码)。类代码的最后一行,创建了一个property对象temperature。简单地说,property将一些代码(get_temperature
和set_temperature
)附加到成员属性(temperature)的访问入口。任何获取temperature值的代码都会自动调用get_temperature()
,而不是去字典表(__dict__
)中进行查找。同样的,任何赋给temperature值的代码也会自动调用set_temperature()
。这是Python中一个很酷的功能。
class Celsius:
def __init__(self, temperature = 0):
print("init value")
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
def get_temperature(self):
print("Getting value")
return self._temperature
def set_temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
# 为_temperature创建property对象,为用户提供外部接口
temperature = property(get_temperature,set_temperature)
if "__main__" == __name__:
c = Celsius() # init value / Setting value
print(c.temperature) # Getting value
c.temperature = 37 # Setting value
c.to_fahrenheit() # Getting value
我们可以看到,通过使用property,我们在不需要客户代码做任何修改的情况下,修改了我们的类,并实现了值约束。因此我们的实现是向后兼容的,这样的结果,大家都很高兴。最后需要注意的是,实际温度值存储在私有变量_temperature
中。属性temperature是一个property对象,是用来为这个私有变量提供接口的。
4.深入挖掘property
在Python中,property()是一个内置函数,用于创建和返回一个property对象。该函数的签名为:
property(fget=None, fset=None, fdel=None, doc=None)
这里,fget是一个获取属性值的函数,fset是一个设置属性值的函数,fdel是一个删除属性的函数,doc是一个字符串(类似于注释)。从函数实现上看,这些函数参数都是可选的。
Property对象有三个方法,getter(), setter()和delete(),用来在对象创建后设置fget,fset和fdel。这就意味着,这行代码:temperature = property(get_temperature,set_temperature)可以被分解为:
# 创建空的property对象
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("Getting value")
return self._temperature
@temperature.setter
def temperature(self, value):
if value < -273:
raise ValueError("Temperature below -273 is not possible")
print("Setting value")
self._temperature = value
@temperature.deleter
def temperature(self):
print("Deleter value")
del self._temperature