@property
是个非常有用的装饰器,它让我们可以基于方法定义类
属性,精确地控制属性的读取、赋值和删除行为,灵活地实现动态属
性等功能。
- 方法(method)表示行为,需要用
()
调用 ,比如obj.method()
。 - 属性(attribute)表示状态,可以直接访问 ,比如
obj.attr
。 @property
装饰器让方法看起来像属性 ,这样就能像obj.attr
这样使用它,而不需要()
。- 但要注意,不是所有方法都适合变成属性 ,尤其是那些计算量大或者需要网络请求的方法 ,因为用户一般期望读取属性时是"秒出"的。
🌟 举个简单的例子
方法和属性的区别:
python
class Person:
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
def get_age(self): # 这是方法
"""计算年龄"""
current_year = 2024
return current_year - self.birth_year
p = Person("Alice", 1990)
print(p.get_age()) # 34(需要用 () 调用)
📌 get_age()
是方法,因为它是一个行为(计算年龄),调用时需要 ()
。
🌟 用 @property
让方法变成属性
python
class Person:
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
@property
def age(self): # 变成了属性
"""计算年龄"""
current_year = 2024
return current_year - self.birth_year
p = Person("Alice", 1990)
print(p.age) # 34(现在不用加 () 了!)
📌 age
变成了"伪装的属性",看起来像 p.age
,但其实是一个方法。
🌟 再看 setter
和 deleter
用 @property
装饰后,我们还可以加 @setter
和 @deleter
,让属性支持修改和删除:
python
class Person:
def __init__(self, name, birth_year):
self.name = name
self.birth_year = birth_year
@property
def age(self):
return 2024 - self.birth_year
@age.setter
def age(self, new_age):
"""通过修改 age 来调整 birth_year"""
self.birth_year = 2024 - new_age
@age.deleter
def age(self):
"""禁止删除 age"""
raise RuntimeError("Can't delete age!")
p = Person("Alice", 1990)
print(p.age) # 34
p.age = 40 # 触发 setter,修改 birth_year
print(p.birth_year) # 1984
del p.age # 触发 deleter,抛出异常
# RuntimeError: Can't delete age!
📌 setter
让 p.age = 40
可以工作 ,实际上它是修改 birth_year
。
📌 deleter
阻止 del p.age
,防止错误操作。
🌟 什么时候不该用 @property
?
如果方法计算量很大,或者涉及网络请求、数据库查询等操作 ,就不应该用 @property
,否则会让用户误以为它是个普通属性,而读取普通属性通常是"瞬间"的。
❌ 不好的例子:
python
class DataFetcher:
@property
def latest_data(self):
"""从 API 获取最新数据(可能要 5 秒!)"""
import time
time.sleep(5) # 模拟慢速 API
return "Latest Data"
df = DataFetcher()
print(df.latest_data) # 需要等 5 秒
📌 这样设计不合理,因为 df.latest_data
读起来像是普通属性,但它实际上很慢!
📌 更好的做法:
python
class DataFetcher:
def get_latest_data(self):
"""从 API 获取最新数据"""
import time
time.sleep(5) # 模拟慢速 API
return "Latest Data"
df = DataFetcher()
print(df.get_latest_data()) # 需要等 5 秒,但用户知道它是个方法
📌 方法 df.get_latest_data()
需要 ()
,能让用户意识到它可能很慢。
🌟 结论
@property
可以把方法变成属性,让代码更优雅 ,比如p.age
。- 可以加
@setter
和@deleter
让属性支持赋值和删除。 - 但计算量大、网络请求等方法 ❌ 不适合用
@property
,否则会让人误解。
开发过程中会用到的场景
🌟 实际开发中 @property
的应用场景
在开发中,@property
常用于封装逻辑,让类的属性更直观。以下是几个实际应用场景:
1️⃣ 计算属性(自动计算值)
💡 场景 :
在交易系统中,Order
类有 unit_price
(单价)和 quantity
(数量),我们想让 total_price
(总价)作为一个属性,而不是方法。
python
class Order:
def __init__(self, unit_price, quantity):
self.unit_price = unit_price
self.quantity = quantity
@property
def total_price(self):
"""计算总价"""
return self.unit_price * self.quantity
order = Order(100, 5)
print(order.total_price) # 500(自动计算)
📌 为什么用 @property
?
- 让
order.total_price
看起来就像普通属性,而不用order.total_price()
,更加直观。 - 自动计算,无需手动更新
total_price
。
2️⃣ 限制属性修改(只读属性)
💡 场景 :
有时候,我们想让某个属性只能读取,不能修改 ,比如 User
类的 email
。
python
class User:
def __init__(self, name, email):
self.name = name
self._email = email # 保护属性,外部不应直接修改
@property
def email(self):
"""只读属性,防止外部修改"""
return self._email
user = User("Alice", "alice@example.com")
print(user.email) # alice@example.com
user.email = "bob@example.com" # ❌ 不能修改
# AttributeError: can't set attribute 'email'
📌 为什么用 @property
?
- 让
email
变成"只读"属性,防止外部修改。 - 如果直接
user._email
,仍然可以修改,但加@property
能起到一定的约束作用。
3️⃣ 属性值格式化(自动处理数据格式)
💡 场景 :
在用户系统中,phone_number
可能有不同的格式(+86, 0086 等),我们可以用 @property
让它始终返回标准格式。
python
class User:
def __init__(self, name, phone):
self.name = name
self._phone = phone
@property
def phone(self):
"""格式化手机号,确保前面有 +86"""
if not self._phone.startswith("+86"):
return "+86 " + self._phone
return self._phone
user = User("Alice", "13800138000")
print(user.phone) # +86 13800138000(自动加上 +86)
📌 为什么用 @property
?
- 让
user.phone
始终返回标准格式,外部使用时不需要关心格式问题。
4️⃣ 动态属性更新(修改一个属性影响另一个属性)
💡 场景 :
一个商品折扣系统 ,price
(原价)和 discount
(折扣)变化时,final_price
(最终价格)需要自动更新。
python
class Product:
def __init__(self, name, price, discount=0):
self.name = name
self.price = price
self.discount = discount # 折扣(百分比)
@property
def final_price(self):
"""计算折扣后的最终价格"""
return self.price * (1 - self.discount / 100)
product = Product("Laptop", 10000, 10)
print(product.final_price) # 9000.0(自动计算折扣价)
product.discount = 20
print(product.final_price) # 8000.0(折扣变化,价格自动更新)
📌 为什么用 @property
?
final_price
不需要存储,每次调用自动计算,保证数据一致性。- 只要
discount
变化,final_price
自动更新,外部不需要手动计算。
5️⃣ 设置属性时触发额外逻辑
💡 场景 :
用户修改密码时,要自动加密,防止存储明文密码。
python
import hashlib
class User:
def __init__(self, username, password):
self.username = username
self._password = self._encrypt(password)
def _encrypt(self, password):
"""简单哈希加密"""
return hashlib.sha256(password.encode()).hexdigest()
@property
def password(self):
"""密码是敏感信息,不允许读取"""
raise ValueError("Password is write-only!")
@password.setter
def password(self, new_password):
"""用户修改密码时自动加密"""
self._password = self._encrypt(new_password)
user = User("Alice", "mypassword")
print(user._password) # 存的是加密后的密码
user.password = "newpassword" # 自动加密
print(user._password) # 新的加密密码
print(user.password) # ❌ 不能读取密码
# ValueError: Password is write-only!
📌 为什么用 @property
?
- 禁止明文存储密码,避免安全问题。
password
是"写入时加密",但不能读取,防止信息泄露。
总结
应用场景 | 使用方式 | 示例代码 |
---|---|---|
计算属性 | 让属性动态计算而不是存储 | total_price = unit_price * quantity |
只读属性 | 防止外部修改某些属性 | @property def email(self): return self._email |
格式化属性 | 让属性值自动调整格式 | phone_number 自动补 +86 |
动态属性更新 | 修改一个属性时,另一个自动变化 | discount 变化时 final_price 自动更新 |
写入时触发逻辑 | 设置属性时自动加密或转换 | password 赋值时自动加密 |
🌟 什么时候适合用 @property
?
✅ 适合:
- 需要 动态计算 而不是存储的属性(如
total_price
)。 - 需要 限制外部修改 的属性(如
email
)。 - 需要 数据格式化 的属性(如
phone_number
)。 - 需要 写入时触发额外逻辑 (如
password
自动加密)。
❌ 不适合:
- 计算量大,或者涉及数据库/网络请求 的方法,比如
fetch_data()
,否则每次访问都会触发耗时操作。