装饰器@property允许将方法转换为类的“虚拟”属性:在用户看来,它看起来像普通属性,但实际上是由Python函数的逻辑管理的。最初,在Python中,所有实例属性都是直接可访问的,但为了支持封装,需要一种管理数据访问的机制,而不改变类的接口。
历史:
在早期版本的Python中,没有明确的机制来限制对属性的访问。封装问题通过约定(例如,下划线)来解决,但任何存储/验证值逻辑的变化都会破坏兼容性。随着@property装饰器的出现,可以将方法声明为属性,配备getter、setter和deleter,同时保持原有的类接口。
问题:
当需要后续添加验证或计算字段值的逻辑时,重新构建整个接口成本太高——必须更改所有访问变量的地方。同时,字段的内部实现不应对类的外部消费者暴露。@property允许轻松“虚拟化”访问。
解决方案:
@property实现了“保护接口的getter/setter”模式:可以添加可计算属性或控制值的设置,而不改变客户端代码。
示例代码:
class Temperature: def __init__(self, celsius): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("温度低于绝对零度!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9 / 5 + 32 # 使用 obj = Temperature(25) print(obj.celsius) # 25 print(obj.fahrenheit) # 77.0 obj.celsius = -300 # ValueError
关键特性:
是否可以在不破坏向后兼容性的情况下,随意向任何类属性添加property?
不可以。如果属性是公共的并且被用作普通变量,则替换为property是透明的。但如果某处通过__dict__访问或通过type(obj.attr)进行类型检查,可能会出现意外行为。
可以仅声明setter而不声明getter吗?
不可以,首先需要getter(带有@property的方法),否则Python不知道将setter“绑定”到哪个属性上。
@property/@setter/@deleter的顺序是什么,为什么?
总是先写@property(它创建属性对象),然后通过方法名写@<name>.setter和@<name>.deleter(它们补充先前创建的property)。
class A: @property def value(self): ... @value.setter def value(self, v): ...
类的字段是公共的,后来在发布公共API后通过property添加了可计算属性。在旧代码中对属性的访问隐式地改变了行为或导致错误。
优点:
缺点:
在设计时字段被定义为私有(带有一个下划线),用户仅通过方法/property进行操作。未来在property中添加新逻辑时,没有改变客户端接口。
优点:
缺点: