@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是方法名称,两个方法(读和写)必须同名,才能被关联为同一个属性的操作逻辑。
这种设计既保留了属性访问的简洁性,又实现了对读写过程的控制。
1274

被折叠的 条评论
为什么被折叠?



