问题历史:
在经典的面向对象编程中,封装是通过私有字段和 getter/setter 实现的,这显得笨重且不符合“Python 风格”。从 Python 2.2 开始,推出了 @property 装饰器,允许像普通属性一样访问 getter 和 setter 方法,实现了优雅的封装与方便的语法。
问题:
没有 property 装饰器时,需要明确地定义访问和设置值的方法(例如 get_x() 和 set_x(val)),这使代码可读性降低,而类的用户也无法避免直接访问内部数据。在重构和更改内部存储或计算值的逻辑时,可能会出现问题。
解决方案:
@property 装饰器允许通过单一语法定义 getter、setter 和 deleter。这种方式简洁、方便,封装了实现细节,并允许透明地更改属性的计算方式,而不会破坏类的接口。
代码示例:
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("温度低于 -273.15°C 是不可能的!") self._celsius = value
关键特点:
@property 装饰器允许将方法作为普通属性访问。可以仅仅设置只读属性,而不允许写入/删除吗?
可以,如果只定义带有 @property 装饰器的方法,而没有 setter 和 deleter,则该属性将仅可读。
class Sample: @property def value(self): return 42
如果 property 的名称与私有属性相同,会发生什么?
通常,property 用作私有属性的“中介”,该私有属性的名称以下划线开头(例如 _x)。应避免这样的名称冲突,否则将导致递归调用:
class Bad: @property def x(self): return self.x # 无限递归
可以仅为类设置 property 吗?
不可以,标准的 @property 在类的实例上工作。要创建类属性,应使用第三方模式或专用库(@classmethod 与 property 不能直接一起使用)。
开发人员直接访问类字段(self.celsius),而不是通过 property 访问。后来添加了验证,但类的使用者仍然可以直接修改私有属性并绕过检查。
优点:
在没有复杂逻辑时,工作简单且快速。
缺点:
封装被破坏,容易导致对象状态不一致,造成混淆。
使用 property 并通过 _celsius 隐藏内部属性。验证、缓存和逻辑集中在 property 内部。
优点:
代码安全,接口稳定 — 可以在不影响类的使用者的情况下更改属性的实现。
缺点:
对于大型复杂对象,如果过度使用 property,可能会增加调试的复杂性。