写在前面:内容参照自《Effective Python》,其实你完全可以直接去看书,什么?你不想自己看书,那么你也可以关注我,我会不定期从书中挑出常用到的有效方法分享出来,这样你就可以一边刷头条,一边学习知识,岂不美哉。

正文

从其他语言转入Python的开发者,可能会在类中明确地实现 getter(获取器)和 setter(设置器)方法。





这种 setter 和 getter 用起来虽然简单,但却不像 Python 的编程风格。



对于就地自增的代码来说,这种用法尤其显得麻烦。



setter 和 getter 等工具方法,确实有助于定义类的接口,它也使得开发者能够更加方便地封装功能、验证用法并限定取值范围。在设计自己的类时,应该认真考虑这些机制,因为自己的类以后可能会逐渐进化,而这些机制可以确保进化过程不会影响原有的调用代码。

但是,对于Python语言来说,基本上不需要手工实现 setter 或 getter 方法,而是应该先从简单的 public 属性开始写起。



改用简单的属性来实现 Resistor 类之后,原地自增操作就变得清晰而自然了。



以后如果想在设置属性的时候实现特殊行为,那么可以改用 @property 修饰器和 setter 方法来做。下面这个子类继承自 Resistor,它在给 voltage(电压)属性赋值的时候,还会同时修改 current(电流)属性。请注意:setter 和 getter 方法的名称必须与相关属性相符,方能使这套机制正常运作。



现在,设置 voltage 属性时,将会执行名为 voltage 的 setter 方法,该方法会更新本对象的current 属性,令其与电压和电阻相匹配。



为属性指定 setter 方法时,也可以在方法里面做类型验证及数值验证。下面定义的这个类,可以保证传入的电阻值总是大于 0 欧姆:



如果传入无效的属性值:



程序就会抛出异常。



给构造器传入无效数值,同样会引发异常。



之所以会抛出异常,是因为 BoundedResistance.init 会调用 Resistor.init,而 Resistor_init 又会执行 self.ohms=-5。这条赋值语句使得 BoundedResistance 中的 @ohms.setter 得到执行,于是,在对象构造完毕之前,程序会先运行 setter 里面的验证代码。

我们甚至可以用 @property 来防止父类的属性遭到修改。



构建好对象之后,如果试图修改 ohms 属性,那就会引发异常。



@property 的最大缺点在于:和属性相关的方法,只能在子类里面共享,而与之无关的其他类,则无法复用同一份实现代码。不过,Python也提供了描述符机制(参见本书第31条),开发者可以通过它来复用与属性有关的逻辑,此外,描述符还有其他一些用法。

最后,要注意用 @property 方法来实现 setter 和 getter 时,不要把程序的行为写得太过奇怪。例如,我们不应该在某属性的 getter 方法里面修改其他属性的值。



上面这种写法,会导致非常古怪的行为。



最恰当的做法是:只在 @property.setter 里面修改相关的对象状态,而且要防止该对象产生调用者所不希望有的副作用。例如,不应该动态地引入模块、不应该执行缓慢的辅助函数,也不应该执行开销比较大的数据库查询操作等。我们所编写的类,要迅速为用户返回简单的属性,而这种属性,也应该和其他 Python 对象一样,非常易于使用。至于那些比较复杂或速度比较慢的操作,还是应该放在普通的方法里面。

要点

  • 编写新类时,应该用简单的public属性来定义其接口,而不要手工实现 set 和 get 方法。
  • 如果访问对象的某个属性时,需要表现出特殊的行为,那就用 @property 来定义这种行为。
  • @property 方法应该遵循最小惊讶原则,而不应产生奇怪的副作用。
  • @property 方法需要执行得迅速一些,缓慢或复杂的工作,应该放在普通的方法里面。