Python @property 装饰器
@property
简化属性访问:Python的@property
装饰器使得在面向对象编程中使用getter和setter方法更简单,提高了代码的可读性和易用性。- 封装和验证:通过getter和setter方法,
@property
装饰器允许封装私有变量(如_temperature
),并在设置属性时实现验证(如温度不能低于-273.15摄氏度)。 - 向后兼容性:使用
@property
装饰器可以保持代码的向后兼容性,即使在类中添加了新的约束或修改,也不需要改变现有代码中对属性的访问方式。
Python编程提供了内置的@property
装饰器,它使得在面向对象编程中使用getter和setter方法变得更加容易。
在详细介绍@property
装饰器是什么之前,让我们先了解为什么首先需要它。
没有Getters和Setters的类
假设我们决定创建一个类,用于存储摄氏度温度,并实现一个将温度转换为华氏度的方法。
实现这一功能的一种方式如下:
class Celsius:
def __init__(self, temperature = 0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
我们可以创建这个类的对象,并按我们希望的方式操纵temperature
属性:
# Python中设置和获取属性的基本方法
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# 创建一个新对象
human = Celsius()
# 设置温度
human.temperature = 37
# 获取温度属性
print(human.temperature)
# 获取to_fahrenheit方法
print(human.to_fahrenheit())
输出
37
98.60000000000001
这里,华氏度转换中的额外小数位是由于Floating Point Arithmetic Error。
因此,每当我们像上面所示分配或检索任何对象属性(如temperature
)时,Python会在对象的内置__dict__
字典属性中搜索它:
print(human.__dict__)
# 输出: {'temperature': 37}
因此,human.temperature
在内部变成了human.__dict__['temperature']
。
使用Getters和Setters
假设我们想要扩展上面定义的Celsius类的可用性。我们知道任何对象的温度不能低于**-273.15**摄氏度。
让我们更新我们的代码来实现这个值约束。
上述限制的一个显而易见的解决方案是隐藏属性temperature
(使其私有)并定义新的getter和setter方法来操纵它。
这可以如下实现:
# 创建Getters和Setter方法
class Celsius:
def __init__(self, temperature=0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# getter方法
def get_temperature(self):
return self._temperature
# setter方法
def set_temperature(self, value):
if value < -273.15:
raise ValueError("温度低于-273.15是不可能的。")
self._temperature = value
正如我们所见,上述方法引入了两个新的get_temperature()
和set_temperature()
方法。
此外,temperature
被替换为_temperature
。在Python中,开头的下划线_
用于表示私有变量。
现在,让我们使用这个实现:
# 创建Getters和Setter方法
class Celsius:
def __init__(self, temperature=0):
self.set_temperature(temperature)
def to_fahrenheit(self):
return (self.get_temperature() * 1.8) + 32
# getter方法
def get_temperature(self):
return self._temperature
# setter方法
def set_temperature(self, value):
if value < -273.15:
raise ValueError("温度低于-273.15是不可能的。")
self._temperature = value
# 创建一个新对象,__init__内部调用set_temperature()
human = Celsius(37)
# 通过getter获取温度属性
print(human.get_temperature())
# 获取to_fahrenheit方法,方法本身调用get_temperature()
print(human.to_fahrenheit())
# 实现新的约束
human.set_temperature(-300)
# 获取to_fahreheit方法
print(human.to_fahrenheit())
输出
37
98.60000000000001
Traceback (most recent call last):
File "<string>", line 30, in <module>
File "<string>", line 16, in set_temperature
ValueError: 温度低于 -273.15 是不可能的。
这次更新成功实现了新的限制。我们不再被允许将温度设置为低于**-273.15**摄氏度。
注意:在Python中实际上不存在私有变量。只是需要遵循的规范。语言本身不施加任何限制。
然而,上述更新的更大问题是,所有实现了我们之前类的程序必须将它们的代码从obj.temperature
修改为obj.get_temperature()
,并将所有类似obj.temperature = val
的表达式改为obj.set_temperature(val)
。
在处理成千上万行代码时,这种重构可能会导致问题。
总而言之,我们的新更新不是向后兼容的。这就是@property
派上用场的时候。
property类
处理上述问题的一种Python风格的方法是使用property
类。以下是我们如何更新代码:
# 使用property类
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# getter
def get_temperature(self):
print("获取值...")
return self._temperature
# setter
def set_temperature(self, value):
print("设置值...")
if value < -273.15:
raise ValueError("温度低于-273.15是不可能的")
self._temperature = value
# 创建一个property对象
temperature = property(get_temperature, set_temperature)
我们在get_temperature()
和set_temperature()
内部添加了print()
函数,以清楚地观察到它们被执行。
代码的最后一行使temperature
成为一个property对象。简单来说,property将一些代码(get_temperature
和set_temperature
)附加到成员属性访问(temperature
)上。
让我们使用这个更新的代码:
# 使用property类
class Celsius:
def __init__(self, temperature=0):
self.temperature = temperature
def to_fahrenheit(self):
return (self.temperature * 1.8) + 32
# getter
def get_temperature(self):
print("获取值...")
return self._temperature
# setter
def set_temperature(self, value):
print("设置值...")
if value < -273.15:
raise ValueError("温度低于-273.15是不可能的")
self._temperature = value
# 创建一个property对象
temperature = property(get_temperature, set_temperature)
human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
human.temperature = -300
输出
设置值...
获取值...
37
获取值...
98.60000000000001
设置值...
Traceback (most recent call last):
File "<string>", line 31, in <module>
File "<string>", line 18, in set_temperature
ValueError: 温度低于 -273 是不可能的
正如我们所见,任何检索temperature
值的代码将自动调用get_temperature()
,而不是字典(dict)查找。
类似地,任何给temperature
分配值的代码将自动调用set_temperature()
。
我们甚至可以看到,在我们创建对象时set_temperature()
就被调用了。
human = Celsius(37) # 打印设置值...
你能猜出为什么吗?
原因是当一个对象被创建时,__init__()
方法被调用。这个方法包含了self.temperature = temperature
这一行。这个表达式自动调用了set_temperature()
。
同样地,像c.temperature
这样的访问自动调用get_temperature()
。这就是property做的事情。
通过使用property
,我们可以看到,在实现值约束时不需要修改。因此,我们的实现是向后兼容的。
注:实际的温度值存储在私有变量_temperature
中。temperature
属性是一个property对象,它为这个私有变量提供了一个接口。
@property装饰器
在Python中,property()
是一个内置函数,用于创建并返回一个property
对象。这个函数的语法是:
property(fget=None, fset=None, fdel=None, doc=None)
这里,
fget
是获取属性值的函数fset
是设置属性值的函数fdel
是删除属性的函数doc
是一个字符串(类似于注释)
从实现中可以看出,这些函数参数都是可选的。
一个property对象有三个方法,getter()
、setter()
和deleter()
,用于在稍后指定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装饰器的程序员可以认出,上述结构可以作为装饰器实现。
我们甚至可以不定义get_temperature
和set_temperature
的名称,因为它们是不必要的,会污染类的命名空间。
为此,我们在定义getter和setter函数时重用temperature
名称。让我们看看如何将其实现为装饰器:
# 使用@property装饰器
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):
print("设置值...")
if value < -273.15:
raise ValueError("温度低于-273是不可能的")
self._temperature = value
# 创建一个对象
human = Celsius(37)
print(human.temperature)
print(human.to_fahrenheit())
coldest_thing = Celsius(-300)
输出
设置值...
获取值...
37
获取值...
98.60000000000001
设置值...
Traceback (most recent call last):
File "", line 29, in
File "", line 4, in __init__
File "", line 18, in temperature
ValueError: 温度低于 -273 是不可能的
上述实现既简单又高效。这是使用property
的推荐方式。