本文整理自《Effective Python 编写高质量 Python 代码的 59 个有效方法》第 29 条:用纯属性取代 get 和 set 方法。
Python 类 public 属性
首先,python 类中的 public 属性可以实现 setter 和 getter 等工具方法功能,动态修改、获取类中的属性:
class Resistor(object):
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0
if __name__ == '__main__':
r1 = Resistor(50e3)
r1.ohms = 10e3
r1.ohms += 5e3
print(r1.ohms)
修改属性时实现特殊行为
如果想在设置属性的时候实现特殊行为(比如顺带着将相关属性一并更新修改),那么可以改用 @property 修饰器和 setter 方法来做,比如下方继承 Resistor 的子类,在给 voltage 赋值时一并将电流值修改赋值:
class Resistor(object):
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0
class VoltageResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
self._voltage = 0
@property
def voltage(self):
return self._voltage
@voltage.setter
def voltage(self, voltage):
self._voltage = voltage
self.current = self._voltage / self.ohms
if __name__ == '__main__':
r2 = VoltageResistance(1e3)
print("Before: %5r amps" % r2.current)
r2.voltage = 10
print("After: %5r amps" % r2.current)
修改属性时做验证限制
为属性指定 setter 方法时,也可以在方法里面做类型验证及数值验证,下方定义了个保证传入电阻始终大于 0 欧姆的类:
class Resistor(object):
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0
class VoltageResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
self._voltage = 0
@property
def voltage(self):
return self._voltage
@voltage.setter
def voltage(self, voltage):
self._voltage = voltage
self.current = self._voltage / self.ohms
class BoundedResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
@property
def ohms(self):
return self._ohms
@ohms.setter
def ohms(self, ohms):
if ohms <= 0:
raise ValueError("%f ohms must be > 0" % ohms)
self._ohms = ohms
if __name__ == '__main__':
r3 = BoundedResistance(1e3)
print(r3.ohms)
r3.ohms = 0
运行结果:
ValueError: 0.000000 ohms must be > 0
1000.0
防止父类属性被修改
class Resistor(object):
def __init__(self, ohms):
self.ohms = ohms
self.voltage = 0
self.current = 0
class FixedResistance(Resistor):
def __init__(self, ohms):
super().__init__(ohms)
@property
def ohms(self):
return self._ohms
@ohms.setter
def ohms(self,ohms):
if hasattr(self,"_ohms"):
raise AttributeError("Can't set attribute")
self._ohms = ohms
if __name__ == '__main__':
r4 = FixedResistance(1e3)
print(r4.ohms)
r4.ohms = 2e3
注意
用 @property 方法实现 setter 和 getter 用法时,不要把程序写得太过奇怪,例如不要再某属性的 getter 方法中去修改其他属性的值,最恰当的方法是只在 @property.setter 里面修改相关的对象状态,并且要防止该对象产生预料之外的副作用。我们所编写的类是要快速修改、返回对象属性,至于比较复杂或速度慢的操作,应该放到普通方法里。
要点
- 编写新类,用简单的 public 属性定义接口,不要手工实现 set 和 get
- @property 在需要访问对象进行特殊行为时使用,应遵循最小惊讶原则,不应产生奇怪的副作用
- @property 方法需要迅速执行,缓慢复杂的工作不要放里面、应该用普通方法来完成