Python @property装饰器全解析
作者:Tipriest_
在 Python 中,@property 是一个非常常用、也很优雅的装饰器,用来把“方法”伪装成“属性”。它最常见的用途是:对属性的访问进行控制,同时保持简洁的属性访问语法。
下面从几个方面帮你系统介绍一下 @property:
一、为什么需要@property?
假设你有一个类,直接暴露公共属性:
class Person:
def __init__(self, name, age):
self.name = name # 公共属性
self.age = age # 公共属性一开始你只是简单存个年龄,后来你发现:
- 想对
age做合法性检查(不能是负数) - 想在设置年龄时做一些额外处理(比如同步更新别的数据)
如果属性是直接暴露的:
p = Person("Tom", 20)
p.age = -5 # 没有任何阻止
你可以改成用方法:
class Person:
def __init__(self, name, age):
self._age = None
self.set_age(age)
def get_age(self):
return self._age
def set_age(self, value):
if value < 0:
raise ValueError("年龄不能为负数")
self._age = value但这样使用时会变成:
p = Person("Tom", 20)
p.set_age(30)
print(p.get_age())
写起来不如属性直观。并且如果项目已经有很多地方写了 p.age,现在改成 p.get_age() / p.set_age() 会造成兼容性问题。
@property 的作用就是:
在“不改变外部访问方式”的前提下,为属性增加 getter/setter 等逻辑。
二、@property的基本用法(只读属性)
最简单的用法是将一个方法变成“只读属性”:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self): # getter
return self._radius使用时:
c = Circle(10) print(c.radius) # 像访问属性一样调用,实际调用的是 radius() 方法 # 输出:10 c.radius = 20 # 这里会报错,因为没有定义 setter # AttributeError: can't set attribute
此时 radius 就是一个只读属性:可以获取,不能直接赋值。
三、定义可读可写属性:@xxx.setter
常见场景是既要读,也要写,并在写入时做检查或处理:
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
"""半径属性:读取时从 _radius 取值"""
return self._radius
@radius.setter
def radius(self, value):
"""设置半径时进行校验"""
if value <= 0:
raise ValueError("半径必须为正数")
self._radius = value使用:
c = Circle(10) print(c.radius) # 10 c.radius = 5 # 实际调用 radius.setter print(c.radius) # 5 c.radius = -1 # ValueError: 半径必须为正数
要点:
@radius.setter中的名字必须和@property的函数名完全一致。- setter 只定义“写逻辑”,读逻辑仍然在
@property的函数里。
四、带有删除逻辑的属性:@xxx.deleter
有时你希望通过 del obj.attr 来触发一些自定义行为,可以用 @xxx.deleter:
class Demo:
def __init__(self, value):
self._value = value
@property
def value(self):
print("获取 value")
return self._value
@value.setter
def value(self, v):
print("设置 value")
self._value = v
@value.deleter
def value(self):
print("删除 value")
del self._value使用:
d = Demo(10) print(d.value) # 获取 value -> 10 d.value = 20 # 设置 value del d.value # 删除 value
五、计算属性:不存储结果,只在访问时计算
@property 不一定要绑定一个内部存储字段,也可以每次访问时动态计算:
import math
class Circle:
def __init__(self, radius):
self._radius = radius
@property
def radius(self):
return self._radius
@radius.setter
def radius(self, r):
if r <= 0:
raise ValueError("半径必须为正数")
self._radius = r
@property
def area(self):
"""面积属性:只读,实时计算"""
return math.pi * self._radius ** 2使用:
c = Circle(10) print(c.area) # 314.159... c.radius = 5 print(c.area) # 会自动变成半径为 5 时的面积
area 不需要额外存储字段 _area,也不需要在每次修改半径时手动更新面积,访问时自动按最新半径计算。
六、命名习惯:前有下划线的“内部属性”
常见写法:
- 公共接口属性:
obj.x - 内部真实存储字段:
obj._x
class Person:
def __init__(self, age):
self._age = None
self.age = age # 这里会走 setter
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("年龄不能为负数")
self._age = value初学者常见错误是 setter 里面写成 self.age = value:
@age.setter
def age(self, value):
self.age = value # 错误!会导致无限递归
正确做法是操作“内部字段”例如 self._age,避免调用自己。
七、@property的优点总结
保持接口稳定
一开始可以简单写成公有属性:
class Person:
def __init__(self, age):
self.age = age
以后如果需要加校验,只要改成:
class Person:
def __init__(self, age):
self._age = None
self.age = age
@property
def age(self):
return self._age
@age.setter
def age(self, value):
if value < 0:
raise ValueError("年龄不能为负数")
self._age = value外部代码依然写 p.age,不需要改任何调用。
语法简洁、可读性好
相比 obj.get_x() / obj.set_x(),obj.x 更符合属性的直觉。
延迟计算 / 动态计算
对一些计算开销不大的数据,用 @property 动态计算非常方便。
如果计算很昂贵,还可以结合缓存(例如自己实现缓存,或用 functools.cached_property)。
方便加入调试日志、权限控制等逻辑
例如在 setter 里打印日志、做权限检查,或者在 getter 里做懒加载等。
八、与传统 getter/setter 的区别
- Java / C++ 风格:显式的
get_xxx()、set_xxx()方法。 - Python:更提倡属性访问风格 +
@property隐藏实现细节。
也就是说,Python 代码里你几乎看不到:
obj.get_age() obj.set_age(20)
而是:
obj.age obj.age = 20
但又不牺牲封装性:内部实现完全可以通过 @property 控制。
九、一些注意事项
- 不要滥用
@property- 若属性只是单纯存取,不需要任何检查或计算,直接公有字段也完全没问题。
- 过度包装会增加心智负担。
- 避免在 getter 中做特别耗时的操作
- 因为访问语法很轻量,调用者不会意识到“这个属性代价这么大”,容易造成性能问题。
- 对耗时计算,建议用普通方法,或者明确注释说明。
- 继承时的覆盖
- 子类可以重写父类的
@property,仍然使用同名属性,这对库设计非常有用。
- 子类可以重写父类的
十、一个综合示例
class BankAccount:
def __init__(self, owner, balance):
self.owner = owner
self._balance = None
self.balance = balance # 走 setter 做校验
@property
def balance(self):
"""账户余额,不能为负数"""
return self._balance
@balance.setter
def balance(self, value):
if value < 0:
raise ValueError("余额不能为负数")
self._balance = float(value)
@property
def is_rich(self):
"""计算属性:根据余额判断是否“富有”"""
return self._balance >= 1_000_000使用:
acc = BankAccount("Alice", 1000)
print(acc.balance) # 1000.0
acc.balance = 2000
print(acc.is_rich) # False
acc.balance = 2_000_000
print(acc.is_rich) # True
acc.balance = -10 # ValueError: 余额不能为负数到此这篇关于Python @property装饰器全解析的文章就介绍到这了,更多相关Python @property装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
