Python @property装饰器用法:怎么实现Getter和Setter
在Python面向对象编程中,实现数据的封装与安全访问并不需要像Java或C++那样强制编写繁琐的 get_value() 和 set_value() 方法。Python内置的 @property 装饰器提供了一种更优雅的解决方案:它允许将类方法伪装成普通属性进行读取和赋值,既保留了直接点号访问(如 obj.price)的简洁性,又赋予了开发者在取值与赋值时执行数据校验、边界控制及计算逻辑的能力。
一、为什么需要 @property
设想一个表示商品的类 Product。如果直接暴露公开属性 price,调用方可以随时执行 product.price = -100,从而注入破坏业务规则的非法数据。在没有属性装饰器时,通常会通过 get_price() 和 set_price() 解决,但这会导致代码冗长;如果最初已发布了直接读取字段的公开API,后续改为方法调用更会导致所有调用方代码破坏性失效。
@property 解决了这种两难困境。它既具备私有变量的安全性,又维持公开属性的调用体验。
二、实现 Getter 和 Setter 的核心三步
在Python中定义基于 @property 的读写属性,通常遵循三个核心步骤:
- 私有变量存储:在
__init__中使用单下划线前缀变量(如_price)保存实际数据。 - 实现Getter:定义与对外公开属性同名的方法,并添加
@property装饰器,负责读取数据。 - 实现Setter:定义相同名称的方法,并添加
@<属性名>.setter装饰器,负责数据校验并存入私有变量。
三、完整代码示例:带有边界校验的属性
下面是一个标准的面向对象类定义,展示如何对学生的测试成绩进行安全管控。成绩必须在 0 到 100 之间,且不能传入非数值类型:
class StudentScore:
def __init__(self, score: float):
# 推荐通过属性赋值,从而在初始化时同样触发 setter 校验
self.score = score
@property
def score(self) -> float:
"""Getter方法:获取成绩"""
return self._score
@score.setter
def score(self, value: float):
"""Setter方法:校验并写入成绩"""
if not isinstance(value, (int, float)):
raise TypeError("成绩必须是数字类型")
if not (0 <= value <= 100):
raise ValueError("成绩必须在 0 到 100 之间")
self._score = float(value)
# 正常调用测试
stu = StudentScore(88.5)
print(stu.score) # 输出示例: 88.5
stu.score = 96.0 # 正常更新
print(stu.score) # 输出示例: 96.0
# 边界异常测试(将抛出 ValueError)
# stu.score = 105
四、底层工作机制:数据流与拦截原理
当执行 stu.score = 96.0 时,Python解释器会在类层次的描述符协议中查找到与 score 绑定的 property 实例。解释器不会直接修改实例命名空间中的字典,而是转为调用该 property 对象的 __set__ 方法,将实例和新值作为参数传入已注册的 setter 函数。
五、只读属性与 Deleter
如果一个属性只声明了 @property 而没有定义 @<属性名>.setter,该属性在外部就成为了只读属性。试图对其赋值将直接抛出 AttributeError:
class Circle:
def __init__(self, radius: float):
self._radius = radius
@property
def area(self) -> float:
"""只读属性:动态计算圆面积"""
return 3.1415926535 * (self._radius ** 2)
c = Circle(5.0)
print(c.area) # 正常计算并输出
# c.area = 50 # 抛出 AttributeError: can't set attribute
另外,还可以使用 @<属性名>.deleter 拦截 del obj.prop 操作,在属性被移除时同步清理资源或重置状态。
六、开发常见注意事项
- 严禁在 Setter 中对公开同名属性赋值:如果在
setter内部写成self.score = value,将再次调用setter本身,导致无限递归并抛出RecursionError。必须赋值给私有变量self._score。 - 装饰器定义顺序:必须先使用
@property定义 Getter,随后才能使用@<属性名>.setter。因为后者是 Getter 创建的 property 实例上的方法。 - 计算密集型属性避免滥用:属性语法让调用者直觉上认为这是一次低成本的取值操作。如果背后涉及高延迟的网络请求或复杂数据库查询,建议显式保留为普通方法,避免产生性能歧义。
七、要点总结
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













