当前位置: 代码网 > it编程>前端脚本>Python > Python @property装饰器全解析

Python @property装饰器全解析

2026年02月07日 Python 我要评论
在 python 中,@property 是一个非常常用、也很优雅的装饰器,用来把“方法”伪装成“属性”。它最常见的用途是:对属性的访问进行控制,同时保

在 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装饰器内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com