Python面向对象:@property装饰器属性访问控制

一、开篇:把方法伪装成属性
在Java中,你会写obj.getName()和obj.setName("张三")——一堆getter和setter方法。Python的做法更优雅:用@property装饰器,让你像访问属性一样调用方法——可以无缝添加验证、计算、惰性加载等逻辑。
⌨️ 先看对比:
# ❌ Java风格——啰嗦
class PersonJavaStyle:
def __init__(self, name):
self._name = name
def get_name(self):
return self._name
def set_name(self, value):
if not value:
raise ValueError("名字不能为空")
self._name = value
# 使用:person.get_name() / person.set_name("李四")
# ✅ Python风格——优雅
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
"""获取名字——像属性一样访问"""
return self._name
@name.setter
def name(self, value):
"""设置名字——带验证"""
if not value:
raise ValueError("名字不能为空")
self._name = value
# 使用:person.name / person.name = "李四"
p = Person("张三")
print(p.name) # 张三 —— 像属性一样读
p.name = "李四" # 像属性一样写 —— 但实际调用了setter
print(p.name) # 李四
二、@property的三种用法
2.1 只读属性
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""半径——只读"""
return self._radius
@property
def area(self):
"""面积——只读计算属性"""
import math
return math.pi * self._radius ** 2
@property
def circumference(self):
"""周长——只读计算属性"""
import math
return 2 * math.pi * self._radius
circle = Circle(5)
print(circle.radius) # 5
print(f"面积: {circle.area:.2f}") # 78.54
print(f"周长: {circle.circumference:.2f}") # 31.42
# circle.radius = 10 # AttributeError —— 没有setter!
# circle.area = 100 # AttributeError —— 没有setter!
2.2 可读写属性(getter + setter)
class Product:
def __init__(self, name, price):
self.name = name
self._price = price
@property
def price(self):
"""价格——getter"""
return self._price
@price.setter
def price(self, value):
"""价格——setter(带验证)"""
if value < 0:
raise ValueError("价格不能为负数")
self._price = value
@property
def discount_price(self):
"""折后价——只读(基于_price计算)"""
return round(self._price * 0.9, 2)
product = Product("笔记本电脑", 5999)
print(product.price) # 5999 —— getter
product.price = 4999 # setter —— 看起来像直接赋值
print(product.price) # 4999
print(product.discount_price) # 4499.1 —— 自动基于最新价格计算
2.3 deleter——删除属性
class User:
def __init__(self, username, password):
self.username = username
self._password = password
@property
def password(self):
"""密码——不可读(安全考虑)"""
raise AttributeError("密码不可直接读取")
@password.setter
def password(self, value):
"""设置密码——带强度检查"""
if len(value) < 8:
raise ValueError("密码至少8位")
self._password = value
@password.deleter
def password(self):
"""删除密码——清除敏感数据"""
print("密码已被清除")
self._password = None
user = User("zhangsan", "old_password")
# print(user.password) # AttributeError: 密码不可直接读取
user.password = "new_secure_password" # setter
del user.password # deleter —— 密码已被清除
三、@property的实际应用
3.1 数据验证
class Student:
def __init__(self, name, age, score):
self.name = name
self.age = age # 会调用setter
self.score = score # 会调用setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if not isinstance(value, int):
raise TypeError("年龄必须是整数")
if not 0 <= value <= 150:
raise ValueError("年龄必须在0-150之间")
self._age = value
@property
def score(self):
return self._score
@score.setter
def score(self, value):
if not 0 <= value <= 100:
raise ValueError("分数必须在0-100之间")
self._score = value
@property
def grade(self):
"""等级——基于score动态计算"""
if self._score >= 90:
return "A"
elif self._score >= 80:
return "B"
elif self._score >= 70:
return "C"
elif self._score >= 60:
return "D"
else:
return "F"
s = Student("张三", 20, 92)
print(f"{s.name}: {s.score}分, 等级{s.grade}") # 张三: 92分, 等级A
3.2 惰性加载
class DataAnalyzer:
"""数据分析器——惰性计算统计信息"""
def __init__(self, data):
self.data = data
self._statistics = None # 缓存
self._sorted_data = None # 缓存
@property
def statistics(self):
"""统计信息——只在第一次访问时计算"""
if self._statistics is None:
print("正在计算统计数据...")
self._statistics = {
"count": len(self.data),
"sum": sum(self.data),
"avg": sum(self.data) / len(self.data),
"min": min(self.data),
"max": max(self.data),
}
else:
print("从缓存获取统计数据")
return self._statistics
@property
def sorted_data(self):
"""排序数据——惰性排序"""
if self._sorted_data is None:
print("正在排序...")
self._sorted_data = sorted(self.data)
else:
print("从缓存获取排序数据")
return self._sorted_data
def add_data(self, value):
"""添加新数据——清除缓存"""
self.data.append(value)
self._statistics = None # 缓存失效
self._sorted_data = None # 缓存失效
analyzer = DataAnalyzer([5, 2, 8, 1, 9, 3])
print(analyzer.statistics) # 计算
print(analyzer.statistics) # 缓存命中
print(analyzer.sorted_data) # 计算(没有缓存)
analyzer.add_data(10)
print(analyzer.statistics) # 重新计算(缓存已清除)
3.3 向后兼容的重构
# 场景:最初用简单属性,后来需要加验证/计算逻辑
# @property让你在保持API不变的情况下升级
# 版本1:简单属性
# class Product:
# def __init__(self, price):
# self.price = price # 直接属性
# 版本2:需要价格验证——保持API不变!
class Product:
def __init__(self, price):
self.price = price # 仍然像直接赋值一样使用!
@property
def price(self):
return self._price
@price.setter
def price(self, value):
if value < 0:
raise ValueError("价格不能为负")
self._price = value
# 新旧代码都不需要改变!
p = Product(100)
print(p.price) # 100 —— 和以前一样
p.price = 200 # 和以前一样 —— 但有了验证!
# p.price = -50 # ValueError —— 新的保护
四、@property的底层原理
# @property本质上是描述符(Descriptor)的语法糖
# 下面的代码:
class MyClass:
@property
def value(self):
return self._value
@value.setter
def value(self, new_value):
self._value = new_value
# 等价于:
class MyClassManual:
def get_value(self):
return self._value
def set_value(self, new_value):
self._value = new_value
value = property(get_value, set_value) # property()构造
# property()的完整签名:
# property(fget=None, fset=None, fdel=None, doc=None)
# fget: getter函数
# fset: setter函数
# fdel: deleter函数
# doc: 文档字符串
五、最佳实践和注意事项
# ✅ 使用@property的场景:
# 1. 需要数据验证
# 2. 计算属性(基于其他属性动态计算)
# 3. 惰性加载
# 4. 向后兼容的重构
# 5. 只读属性
# ⚠️ 不要使用@property的场景:
# 1. 有副作用的操作(读属性不应该修改状态)
# @property
# def latest_data(self):
# self._fetch_from_api() # ❌ 副作用!读属性不应该触发网络请求
# return self._data
# 2. 耗时操作(读属性应该很快)
# @property
# def complex_report(self):
# time.sleep(5) # ❌ 太慢了!用方法代替
# return "report"
# 3. 需要传参数时(用方法代替)
# @property
# def data_by_date(self): # ❌ 不能传参数!
# pass
# def get_data(self, date): # ✅ 用方法
# pass
# 💡 简单判断:如果访问感觉像"读一个属性",用@property
# 如果感觉像"执行一个动作",用方法
六、总结
@property是Python最优雅的特性之一——它让方法看起来像属性,让你在保持简洁API的同时,拥有方法的所有能力(验证、计算、缓存)。
💡 核心要点:
| 装饰器 | 作用 | 调用方式 |
|---|---|---|
@property | getter | obj.attr |
@attr.setter | setter | obj.attr = value |
@attr.deleter | deleter | del obj.attr |
✅ 黄金法则:对外像属性一样简单,对内像方法一样灵活。@property是Python封装的最佳拍档。

3万+

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



