Python有一个伟大的概念,称为属性,它使面向对象的程序员的生活变得更加简单。
在定义和详细了解@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() >>> # 设定温度 >>> man.temperature = 37 >>> # 得到温度 >>> man.temperature 37 >>> # 获得华氏度 >>> man.to_fahrenheit() 98.60000000000001
转换为华氏温度时,多余的小数位是由于浮点运算错误(在Python解释器中尝试1.1 + 2.2)。
如上所示,每当我们分配或检索任何对象属性(如temperature)时,Python都会在对象的__dict__字典中进行搜索。
>>> man.__dict__ {'temperature': 37}
因此,man.temperature内部变为man.__dict__['temperature']。
现在,让我们进一步假设我们的课程在客户中很受欢迎,并且他们开始在程序中使用它。 他们对对象进行了各种分配。
有一天,一个值得信赖的客户来找我们,建议温度不能低于-273摄氏度(热力学专业的学生可能会说实际上是-273.15摄氏度),也被称为绝对零度。他进一步要求我们实现这个值约束。作为一家追求客户满意度的公司,我们很高兴地听取了这个建议,并发布了1.01版本(对现有类的升级)。
解决上述约束的一个明显方法是隐藏属性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: Temperature below -273 is not possible >>> c = Celsius(37) >>> c.get_temperature() 37 >>> c.set_temperature(10) >>> c.set_temperature(-300) Traceback (most recent call last): ... ValueError: Temperature below -273 is not possible
此更新成功实施了新限制。我们不再被允许将温度设置为低于-273。
请注意,私有变量在Python中不存在。只需遵循一些规范即可。语言本身没有任何限制。
>>> c._temperature = -300 >>> c.get_temperature() -300
但这不是一个大问题。上述更新的最大问题在于,所有在程序中实现了上一类的客户端都必须将其代码从obj.temperature修改为obj.get_temperature(),并将所有分配(例如obj.temperature = val修改为obj.set_temperature( val))。
这种重构会给客户带来数十万行代码的麻烦。
总而言之,我们的新更新不向后兼容。这是@property发挥作用的地方。
python处理上述问题的方法是使用property。我们可以这样来实现它。
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对象temperature。简而言之,属性将一些代码(get_temperature和set_temperature)附加到成员属性访问(temperature)。
任何检索温度值的代码都将自动调用get_temperature()而不是字典(__dict__)查找。 同样,任何为温度分配值的代码都会自动调用set_temperature()。 这是Python中的一项很酷的功能。
我们可以在上面看到即使创建对象时也会调用set_temperature()。
你能猜出为什么吗?
原因是创建对象时,将调用__init __()方法。 此方法的线为self.temperature = temperature。 此分配自动称为set_temperature()。
>>> c.temperature Getting value 0
同样,任何访问如c.temperature都会自动调用get_temperature()。 这就是属性的作用。 这里还有一些实例。
>>> c.temperature = 37 Setting value >>> c.to_fahrenheit() Getting value 98.60000000000001
通过使用属性,我们可以看到,我们修改了类并实现了值约束,而无需更改客户端代码。因此,我们的实现是向后兼容的。
最后请注意,实际温度值存储在私有变量_temperature中。 temperature属性是一个属性对象,它提供了与此私有变量的接口。
在Python中,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中装饰器的程序员可以认识到上述构造可以实现为装饰器。
我们可以更进一步,不定义名称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中寻找属性时,您很可能会遇到这些类型的构造。
好,今天就这样。