Python @property 装饰器
在本教程中,您将学习 Python @property 装饰器;在面向对象编程中使用 getter 和 setter 的 Pythonic 方式。
Python 编程为我们提供了一个内置的@property
装饰器,它使得在面向对象编程中更容易使用 getter 和 setter。
在详细介绍什么 @property
之前 装饰器是,让我们首先建立一个关于为什么首先需要它的直觉。
没有 Getter 和 Setter 的类
让我们假设我们决定创建一个以摄氏度为单位存储温度的类。它还将实现一种将温度转换为华氏度的方法。一种方法如下:
classCelsius:def __init__(self, temperature =0):self.temperature =temperature def to_fahrenheit(self):return (self.temperature * 1.8) + 32
我们可以用这个类制作对象并操纵温度
我们希望的属性:
# Pythonclass中设置和获取属性的基本方法Celsius:def __init__(self, temperature=0):self.temperature =temperature def to_fahrenheit(self):return (self. temperature * 1.8) + 32# 创建一个新对象human =Citizen()# 设置温度human.temperature =37# 获取温度属性print(human.temperature)# 获取to_fahrenheit 方法print(human.to_fahrenheit())
输出
3798.60000000000001
转换为华氏度时的额外小数位是由于浮点算术错误造成的。要了解更多信息,请访问 Python 浮点运算错误。
每当我们分配或检索任何对象属性时,例如 temperature
如上所示,Python在对象的内置__dict__
中搜索 字典属性。
>>> human.__dict__{'temperature':37}
因此,man.temperature
内部变成man.__dict__['temperature']
.
使用 Getter 和 Setter
假设我们想要扩展 Celsius 的可用性 上面定义的类。我们知道任何物体的温度都不能低于-273.15摄氏度(热力学中的绝对零)
让我们更新我们的代码来实现这个值约束。
上述限制的一个明显解决方案是隐藏属性 temperature
(将其设为私有)并定义新的 getter 和 setter 方法来操作它。这可以按如下方式完成:
# 制作 Getters 和 Setter 方法类Celsius:def __init__(self, temperature=0):self.set_temperature(temperature) def to_fahrenheit(self):return (self.get_temperature() * 1.8) + 32 # getter method def get_temperature(self):return self._temperature # setter method def set_temperature(self, value):if value <-273.15:raise ValueError("温度低于-273.15是不可能的。") self._temperature =value
可以看到,上面的方法引入了两个新的get_temperature()
和 set_temperature()
方法。
此外,温度
被替换为 _temperature
.下划线 _
开头用于表示Python中的私有变量。
现在,让我们使用这个实现:
# 制作 Getters 和 Setter 方法类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#创建一个新对象,set_temperature()被__init__human内部调用=Celsius(37)#通过getterprint(human.get_temperature())获取温度属性#获取to_fahrenheit方法,get_temperature()方法调用自身print(human.to_fahrenheit())#新约束实现human.set_temperature(-300)#获取to_fahreheit方法print(human.to_fahrenheit())
输出
3798.60000000000001Traceback(最近一次调用最后一次):文件“”,第 30 行,在 文件“ ”,第 16 行,在 set_temperatureValueError 中:温度低于 -273.15 是不可能的。
此更新成功实施了新限制。我们不再允许设置低于-273.15摄氏度的温度。
注意 :私有变量实际上并不存在于 Python 中。只需遵循一些规范即可。语言本身没有任何限制。
>>> human._temperature =-300>>> human.get_temperature()-300
然而,上述更新的更大问题是所有实现我们之前类的程序都必须从 obj.temperature
修改它们的代码 obj.get_temperature()
以及像 obj.temperature =val
这样的所有表达式 obj.set_temperature(val)
.
在处理数十万行代码时,这种重构可能会导致问题。
总而言之,我们的新更新不向后兼容。这是@property
来救援。
属性类
处理上述问题的pythonic方法是使用property
班级。以下是我们如何更新我们的代码:
# using property classclassCelsius:def __init__(self, temperature=0):self.temperature =temperature def to_fahrenheit(self):return (self.temperature * 1.8) + 32 # getter def get_temperature(self) :print("Getting value...") return self._temperature # setter def set_temperature(self, value):print("Setting value...") if value <-273.15:raise ValueError("Temperature below -273.15 is不可能") self._temperature =value # 创建一个属性对象 temperature =property(get_temperature, set_temperature)
我们添加了一个 print()
get_temperature()
内的函数 和 set_temperature()
清楚地观察到它们正在被执行。
最后一行代码制作了一个属性对象temperature
.简单的说,property 附加了一些代码(get_temperature
和 set_temperature
) 到成员属性访问 (温度
).
让我们使用这个更新代码:
# using property classclassCelsius:def __init__(self, temperature=0):self.temperature =temperature def to_fahrenheit(self):return (self.temperature * 1.8) + 32 # getter def get_temperature(self):print("Getting value...") return self._temperature # setter def set_temperature(self, value):print("Setting value...") if value <-273.15:raise ValueError (“温度低于-273.15 是不可能的”) self._temperature =value # 创建一个属性对象温度 =property(get_temperature, set_temperature)human =摄氏度(37)print(human.temperature)print(human.to_fahrenheit())human .温度 =-300
输出
设置值...正在获取值...37正在获取值...98.60000000000001正在设置值...回溯(最近一次调用最后一次):文件“”,第 31 行,在 文件中“ ”,第 18 行,在 set_temperatureValueError 中:温度低于 -273 是不可能的
如我们所见,任何检索温度
值的代码 会自动调用get_temperature()
而不是字典(__dict__)查找。类似地,任何给温度
赋值的代码 会自动调用set_temperature()
.
我们甚至可以在上面看到 set_temperature()
即使在我们创建对象时也被调用。
>>> human =Celsius(37)设置值...
你能猜出为什么吗?
原因是在创建对象时,__init__()
方法被调用。这个方法有一行 self.temperature =temperature
.这个表达式会自动调用set_temperature()
.
类似地,任何像 c.temperature
这样的访问 自动调用get_temperature()
.这就是财产的作用。这里再举几个例子。
>>> human.temperatureGetting value37>>> human.temperature =37Setting value>>> c.to_fahrenheit()Getting value98.60000000000001
通过使用 property
,我们可以看到在值约束的实现中不需要修改。因此,我们的实现是向后兼容的。
注意 :实际温度值存储在私有_temperature
多变的。 温度
attribute 是一个属性对象,它为这个私有变量提供了一个接口。
@property 装饰器
在 Python 中,property()
是一个内置函数,它创建并返回一个 property
目的。这个函数的语法是:
property(fget=None, fset=None, fdel=None, doc=None)
其中,
fget
是获取属性值的函数fset
是设置属性值的函数fdel
是删除属性的函数doc
是一个字符串(如注释)
从实现中可以看出,这些函数参数是可选的。因此,可以简单地创建一个属性对象,如下所示。
>>> property()<0x00000000003239B38处的属性对象>
一个属性对象有三个方法,getter()
, setter()
, 和 deleter()
指定 fget
, fset
和 fdel
稍后。这意味着,该行:
温度 =属性(获取温度,设置温度)
可以分解为:
# make empty propertytemperature =property()#assign fgettemperature =temperature.getter(get_temperature)#assign fsettemperature =temperature.setter(set_temperature)
这两段代码是等价的。
熟悉 Python 装饰器的程序员可以认识到,上述构造可以实现为装饰器。
我们甚至不能定义名称 get_temperature
和 set_temperature
因为它们是不必要的并且会污染类命名空间。
为此,我们重用了温度
在定义我们的 getter 和 setter 函数时命名。让我们看看如何将其实现为装饰器:
# 使用@property 装饰器类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):print("Setting value...") 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正在设置值...回溯(最近一次调用最后一次):文件“”,第 29 行,在 文件中“ ”,第 4 行,在 __init__ 文件“ ”,第 18 行,在 temperatureValueError 中:温度低于 -273 是不可能的 字符串> 模块> 字符串>
以上实现简单高效。这是使用 property
的推荐方式 .
Python
-
Python XML Parser Tutorial:Read xml file example(Minidom, ElementTree)
-
Python 条件语句:IF...Else、ELIF 和 Switch Case