Python @property装饰器用法:怎么实现Getter和Setter

清欢
清欢
发布时间:2026-10-10 | 1人浏览过
原创

在Python面向对象编程中,实现数据的封装与安全访问并不需要像Java或C++那样强制编写繁琐的 get_value() 和 set_value() 方法。Python内置的 @property 装饰器提供了一种更优雅的解决方案:它允许将类方法伪装成普通属性进行读取和赋值,既保留了直接点号访问(如 obj.price)的简洁性,又赋予了开发者在取值与赋值时执行数据校验、边界控制及计算逻辑的能力。

Python @property装饰器用法:怎么实现Getter和Setter
图1:传统方法调用模式与 Python @property 属性化接口模型对比

一、为什么需要 @property

设想一个表示商品的类 Product。如果直接暴露公开属性 price,调用方可以随时执行 product.price = -100,从而注入破坏业务规则的非法数据。在没有属性装饰器时,通常会通过 get_price() 和 set_price() 解决,但这会导致代码冗长;如果最初已发布了直接读取字段的公开API,后续改为方法调用更会导致所有调用方代码破坏性失效。

@property 解决了这种两难困境。它既具备私有变量的安全性,又维持公开属性的调用体验。

二、实现 Getter 和 Setter 的核心三步

在Python中定义基于 @property 的读写属性,通常遵循三个核心步骤:

  1. 私有变量存储:在 __init__ 中使用单下划线前缀变量(如 _price)保存实际数据。
  2. 实现Getter:定义与对外公开属性同名的方法,并添加 @property 装饰器,负责读取数据。
  3. 实现Setter:定义相同名称的方法,并添加 @<属性名>.setter 装饰器,负责数据校验并存入私有变量。
Python @property装饰器用法:怎么实现Getter和Setter
图2:实现基于属性封装的三个标准步骤流程

三、完整代码示例:带有边界校验的属性

下面是一个标准的面向对象类定义,展示如何对学生的测试成绩进行安全管控。成绩必须在 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 函数。

Python @property装饰器用法:怎么实现Getter和Setter
图3:属性赋值触发的描述符拦截及私有字段写入链路

五、只读属性与 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 实例上的方法。
  • 计算密集型属性避免滥用:属性语法让调用者直觉上认为这是一次低成本的取值操作。如果背后涉及高延迟的网络请求或复杂数据库查询,建议显式保留为普通方法,避免产生性能歧义。

七、要点总结

Python @property装饰器用法:怎么实现Getter和Setter
图4:Python @property 机制与开发准则全景总结
相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表