python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Python @property装饰器

Python @property装饰器全解析

作者:Tipriest_

在Python中,@property 是一个非常常用、也很优雅的装饰器,用来把“方法”伪装成“属性”,这篇文章主要介绍了Python @property装饰器介绍,需要的朋友可以参考下

在 Python 中,@property 是一个非常常用、也很优雅的装饰器,用来把“方法”伪装成“属性”。它最常见的用途是:对属性的访问进行控制,同时保持简洁的属性访问语法

下面从几个方面帮你系统介绍一下 @property

一、为什么需要@property?

假设你有一个类,直接暴露公共属性:

class Person:
    def __init__(self, name, age):
        self.name = name      # 公共属性
        self.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: 半径必须为正数

要点:

四、带有删除逻辑的属性:@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,也不需要在每次修改半径时手动更新面积,访问时自动按最新半径计算。

六、命名习惯:前有下划线的“内部属性”

常见写法:

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 的区别

也就是说,Python 代码里你几乎看不到:

obj.get_age()
obj.set_age(20)

而是:

obj.age
obj.age = 20

但又不牺牲封装性:内部实现完全可以通过 @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装饰器内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文