🏠 Python属性装饰器@property实战:优雅管理类属性
📌 引言
在Python面向对象编程中,@property装饰器是一个强大而优雅的工具。它允许我们将方法当作属性来访问,同时保留对数据的控制和验证能力。本文将深入讲解@property的工作原理、使用场景和实战技巧。
🎯 为什么需要@property?
传统的方式中,类的属性直接暴露给外部,这会带来两个问题:
- 数据无法验证:外部可以随意赋任何值,可能导致数据不合法
- 修改接口代价大:后期需要添加校验逻辑时,必须修改所有调用处
看一个反面例子:
class Student:
def __init__(self, name, score):
self.name = name
self.score = score # 可以赋任何值,包括负数!
s = Student("小明", 150) # 分数超过100了!
s.score = -50 # 负数分数?没问题!
💡 @property的基本用法
@property让我们可以把方法变成"属性"来访问:
class Student:
def __init__(self, name, score):
self._name = name
self._score = score
@property
def score(self):
"""获取分数"""
return self._score
@score.setter
def score(self, value):
"""设置分数,带验证"""
if not isinstance(value, (int, float)):
raise TypeError("分数必须是数字")
if 0 <= value <= 100:
self._score = value
else:
raise ValueError("分数必须在0-100之间")
# 使用
s = Student("小明", 85)
print(s.score) # 访问属性,实际调用score()方法
s.score = 92 # 设置属性,实际调用setter
# s.score = 150 # 报错:ValueError: 分数必须在0-100之间
🔧 三个装饰器详解
@property实际上可以分解为三个装饰器:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""getter:获取半径"""
print("调用getter")
return self._radius
@radius.setter
def radius(self, value):
"""setter:设置半径(带验证)"""
print("调用setter")
if value < 0:
raise ValueError("半径不能为负数")
self._radius = value
@radius.deleter
def radius(self):
"""deleter:删除半径"""
print("调用deleter")
del self._radius
# 测试
c = Circle(5)
print(c.radius) # 触发getter
c.radius = 10 # 触发setter
del c.radius # 触发deleter
📐 实战:只读属性与计算属性
1. 只读属性(省略setter)
class Rectangle:
def __init__(self, width, height):
self._width = width
self._height = height
@property
def area(self):
"""面积是计算得出的,不能设置"""
return self._width * self._height
@property
def perimeter(self):
"""周长也是只读的"""
return 2 * (self._width + self._height)
r = Rectangle(4, 5)
print(f"面积: {r.area}") # 20
print(f"周长: {r.perimeter}") # 18
# r.area = 30 # 报错:AttributeError: can't set attribute
2. 缓存计算属性(延迟计算+缓存)
class DataProcessor:
def __init__(self, data):
self._data = data
self._cache = {}
@property
def statistics(self):
"""复杂统计,结果缓存"""
if 'stats' not in self._cache:
print("计算统计信息...")
self._cache['stats'] = {
'sum': sum(self._data),
'avg': sum(self._data) / len(self._data),
'max': max(self._data),
'min': min(self._data)
}
return self._cache['stats']
dp = DataProcessor([1, 2, 3, 4, 5])
print(dp.statistics) # 第一次计算
print(dp.statistics) # 从缓存获取,不重复计算
🎨 进阶技巧
属性代理(委托)
class User:
def __init__(self, first_name, last_name):
self._first_name = first_name
self._last_name = last_name
@property
def full_name(self):
return f"{self._first_name} {self._last_name}"
@full_name.setter
def full_name(self, value):
parts = value.split()
if len(parts) == 2:
self._first_name, self._last_name = parts
else:
raise ValueError("全名必须包含姓和名")
u = User("张", "三")
print(u.full_name) # 张 三
u.full_name = "李 四"
print(u._first_name) # 李
⚠️ 注意事项与最佳实践
- 不要滥用:简单的数据存储不需要@property,增加代码复杂度
- setter不是必须的:只读属性可以只定义@property,不定义setter
- 命名规范:内部存储用单下划线前缀(_name)避免递归
- 文档字符串:给property添加docstring,IDE会显示提示
📝 总结
| 特性 | 作用 |
|---|---|
| @property | 将方法转为只读属性 |
| @xxx.setter | 定义可写属性的赋值逻辑 |
| @xxx.deleter | 定义属性删除时的行为 |
@property是Python"鸭子类型"哲学的体现:用起来像属性,背后却可以是任意复杂的逻辑。掌握它,你的类设计将更加Pythonic!
📚 参考资料
- Python官方文档 - property()函数
- 《Python Cookbook》第9章:元编程
- 《流畅的Python》第19章:属性描述符
(本文由AI生成,仅供学习参考)

浙公网安备 33010602011771号