你将了解Python中的 @property特性, 以pythonic的方式使用getter和setter。
目录表
从一个例子开始
使用 Getter 和 Setter
@property的力量
深入理解Property
Python有一个很棒的概念,叫做property,它使面向对象的程序员的生活更加简单。
在定义和研究@property是什么之前,让我们先直观感受下为什么首先应该使用它。
从一个例子开始
我们假设你决定创建一个可以以摄氏度存储温度的类[1],它还将实现一个将温度转换为华氏度的方法。一种方法是像下面这样做。
我们可以使用这个类创建对象,并按照我们的意愿操作其属性temperature。你可以在Python shell上试试这些操作。
在转换成华氏温度时,由于浮点运算错误(请在Python解释器中尝试1.1 + 2.2),小数点多了一位。
当我们赋值或检索任何对象属性像temperature时,如上面所示,Python都会在该对象的__dict__字典中搜索它。
因此,man.temperature在内部会变成man.__dict__['temperature']。
现在,让我们进一步假设我们的类在客户中很受欢迎,他们开始在自己的程序中使用它。他们对这个对象做了各种各样的赋值操作。
有一天,一个值得信赖的客户来找我们,建议温度不能低于-273摄氏度(热力学专业的学生可能会说实际上是-273.15摄氏度),也被称为绝对零度。他还要求我们实现这个值约束。作为一家追求客户满意的公司,我们很高兴地采纳了这个建议,并发布了1.01版本(对现有的类进行升级)。
使用Getter和Setter
上述对值进行约束的一个明显解决方案是隐藏属性temperature(使其私有)并定义新的 getter 和 setter 接口来操作它。可以按照下面这样做。
从上面可以看出,我们定义了get_temperature()和set_temperature()两个新方法,并且用_temperature替换了temperature。在Python中,开头的下划线 (_) 用于表示私有变量。
这次更新成功地实现了新的限制。我们没法再将温度设置在-273度以下。
请注意,Python中不存在私有变量,但还是有一些简单的准则可以遵循。Python语言本身并不会做限制。
但这并不是什么大问题。上述更新的一个大问题是,所有在其程序中实现我们前面的类的客户都必须修改他们的代码,将obj.temperature修改为obj.get_temperature(),并且将像obj.temperature = val的所有赋值语句修改为obj.set_temperature(val)。
这种重构可能会给客户带来数十多万行代码的麻烦。
总之,我们的新更新不向后兼容。这时property就派上用场了。
@property的力量
处理上述问题的Pythonic的方法是使用property。以下是我们如何实现它。
然后,在shell中运行以下代码并注意观察。
我们在get_temperature()和set_temperature()中添加了一个print()函数,来清楚地观察它们是否正在执行。
代码的最后一行创建了一个property对象temperature。简单地说,property将一些代码(get_temperature和set_temperature)附加到成员属性访问中(temperature)。
任何检索temperature值的代码都将自动调用get_temperature(),而不是使用字典(__dict__)进行查找。类似地,任何对temperature赋值的代码都会自动调用set_temperature()。这是Python中一个很酷的特性。
我们可以看到上面的set_temperature()即使在创建对象时也被调用。
你能猜到这是为什么吗?
原因是在创建对象时,__init__()方法被调用。这个方法有self.temperature = temperature这行代码,所以赋值时会自动调用set_temperature()。
类似地,任何访问,比如c.temperature,都会自动调用get_temperature()。这就是property的作用。这里有更多的几个例子。
通过使用 property,可以看到,我们修改了类并实现了对值的约束,而不需要对客户代码进行任何更改。因此,我们的实现是向后兼容的,皆大欢喜。
最后请注意,实际的温度值存储在私有变量_temperature中。属性temperature是一个property对象,它为这个私有变量提供接口。
深入理解 Property
在Python中,property()是一个内置函数,用于创建和返回一个property对象。这个函数的签名是:
其中,fget是获取属性值的函数,fset是设置属性值的函数,fdel是删除属性的函数,doc是字符串(像注释一样)。从上述实现中可以看出,这些函数参数是可选的。因此,可以按照以下方式创建property对象。
property对象有三个方法,getter()、setter()和deleter(),用于后续指定fget、fset和fdel。这意味着本行代码
可以分解为
这两段代码是等价的。
熟悉Python中的装饰器[2]的程序员可能会意识到,上面的构造可以实现为装饰器。
我们可以继续,不去定义get_temperature和set_temperature名称,因为它们是不必要的,并且会污染类命名空间。为此,我们在定义getter和setter函数时重用了名称temperature。这就是它实现的方式。
上述实现是既简单又推荐使用的创建property的方法。当你在Python中寻找property时,你很可能会遇到这些类型的构造。
好了,今天就到这里。
领取专属 10元无门槛券
私享最新 技术干货