python装饰器 @property

@property 让我们可以把一个方法变成属性访问(不需要写括号),从而封装内部细节,保护数据,同时让接口更友好 .

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        """修改半径,带校验"""
        if value < 0:
            raise ValueError("无效的圆,半径必须是正数")
        self._radius = value



c = Circle(2)
print(c.radius)
c.radius = 5
print(c.radius)
c.radius = -5
print(c.radius)

Traceback (most recent call last):
  File "D:\pythonproject\ChatGPT-learn\面向对象\03property.py", line 22, in <module>
    c.radius = -5
    ^^^^^^^^
  File "D:\pythonproject\ChatGPT-learn\面向对象\03property.py", line 13, in radius
    raise ValueError("无效的圆,半径必须是正数")
ValueError: 无效的圆,半径必须是正数
2
5

 这种使用 @property 和 setter 方法的设计是 Python 中实现封装的经典方式,主要有以下几个好处

1. 控制属性访问,确保数据合法性

通过 setter 方法可以对赋值进行校验(如示例中检查半径是否为正数),避免非法值进入对象。
例如:如果直接暴露 _radius 让外部修改(circle._radius = -5),会导致半径为负数的无效圆;而通过 radius setter 可以直接拦截并抛出错误,保证对象数据始终合法。

2. 隐藏实现细节,保持接口稳定性

  • 外部代码通过 circle.radius 访问 / 修改半径,无需关心内部是用 _radius 还是其他变量存储(比如未来可能改为用直径存储:self._diameter = value * 2)。
  • 即使内部实现变化(如存储方式、校验逻辑修改),外部调用方式(circle.radius)可以保持不变,减少对外部代码的影响。

3. 模拟 "只读" 属性(可选)

如果只定义 @property 而不定义 setter,就可以实现只读属性:

class Circle:
    def __init__(self, radius):
        self._radius = radius
    
    @property
    def radius(self):
        return self._radius  # 只有 getter,没有 setter

# 外部无法修改 radius
c = Circle(5)
c.radius = 10  # 报错:AttributeError: can't set attribute

4. 提供统一的访问接口

无论属性是直接存储(如 _radius)还是动态计算(如面积),都可以通过相同的属性语法访问:

class Circle:
    # ... 已有代码 ...
    
    @property
    def area(self):
        """面积是动态计算的,不是直接存储的"""
        return 3.14 * self._radius **2

c = Circle(2)
print(c.radius)  # 直接存储的属性
print(c.area)    # 动态计算的属性,接口一致

 

总结

这种设计的核心是在保持简单接口(类似直接访问属性)的同时,增强代码的可控性和灵活性。既避免了直接暴露内部变量导致的数据混乱,又比传统的 get_radius()/set_radius() 方法更符合 Python 的简洁风格.

注意

@radius.setter 必须配合 @property 使用,且 radius 是方法(函数)的名称,二者存在严格的依赖关系。具体说明如下:

1. @radius.setter 必须依赖 @property 存在

  • @property 负责将一个方法(如 def radius(self))转换为 “可读属性”,允许通过 实例.radius 的方式访问(而非 实例.radius())。
  • @radius.setter 是基于已定义的 @property 衍生出的 “写属性” 装饰器,其作用是为同一个 radius 方法绑定赋值逻辑。
    如果没有先定义 @property,直接使用 @radius.setter 会报错(Python 会提示找不到对应的 property)。

2. radius 是方法名称,而非普通变量

  • 代码中 def radius(self) 定义的是一个方法,通过 @property 装饰后,它被 “伪装” 成了一个属性,允许用 实例.radius 访问(内部实际调用该方法)。
  • @radius.setter 装饰的 def radius(self, value) 是同一个方法名的重载,专门处理赋值逻辑(当执行 实例.radius = 值 时调用)。
    这两个方法必须同名(都叫 radius),才能被识别为同一个属性的 “读” 和 “写” 逻辑。
class Circle:
    def __init__(self, radius):
        self._radius = radius

    # 第一步:定义“读”逻辑,通过 @property 转换为属性
    @property
    def radius(self):  # 方法名 radius
        return self._radius

    # 第二步:基于同名 property 定义“写”逻辑
    @radius.setter  # 必须与 property 同名(radius)
    def radius(self, value):  # 方法名必须也是 radius
        if value <= 0:
            raise ValueError("半径必须是正数")
        self._radius = value

 

  • 当执行 c.radius 时,实际调用 @property 装饰的 radius(self) 方法(读操作)。
  • 当执行 c.radius = 5 时,实际调用 @radius.setter 装饰的 radius(self, value) 方法(写操作)。

总结

  • @xxx.setter 必须以 @xxx@property 装饰的方法)为前提,二者是 “读写配对” 的关系。
  • xxx 是方法名称,两个方法(读和写)必须同名,才能被关联为同一个属性的操作逻辑。
    这种设计既保留了属性访问的简洁性,又实现了对读写过程的控制。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值