Python @property 装饰器

Python @property 语法

@property 是 Python 的一个内置装饰器,它是 Python 中用于创建属性的 “语法糖”。

@property 允许我们将类的方法转换为像属性一样访问的特殊方法,从而在不改变外部代码调用方式的前提下,对属性的获取(getter)、设置(setter)或删除(deleter)等操作执行特定的逻辑。

语法:

class MyClass:
    def __init__(self, value):
        self._value = value  # 内部私有变量,按约定加下划线

    @property
    def my_attribute(self):
        '''
        Getter 方法,用于获取属性值。
        此方法的文档字符串将成为属性的文档字符串。
        '''
        # 返回属性值
        return self._value

    @my_attribute.setter
    def my_attribute(self, new_value):
        '''
        Setter 方法,用于设置属性值。
        在设置值时可以执行验证或其他逻辑。
        '''
        # 设置属性值
        self._value = new_value

    @my_attribute.deleter
    def my_attribute(self):
        '''
        Deleter 方法,用于删除属性。
        在删除时可以执行清理逻辑。
        '''
        # 删除属性
        del self._value

说明:

@property 装饰器由以下 3 部分组成。

  • @property (Getter):直接放在一个方法上方,会将该方法转变为一个只读属性(getter)。当外部代码尝试读取(访问)这个属性时,就会调用被 @property 装饰的方法。
  • @<attribute_name>.setter (Setter):紧随 @property 定义的 getter 之后,放在一个同名的方法上方。当外部代码尝试修改(赋值)这个属性时,就会调用被 @setter 装饰的方法。该方法会接收两个参数:实例本身(self)和要设置的新值。
  • @<attribute_name>.deleter (Deleter):紧随 @property 定义的 getter 之后,放在一个同名的方法上方。当外部代码尝试使用 del 关键字删除这个属性时,就会调用被 @deleter 装饰的方法。该方法只接收一个参数:实例本身(self)。

提示:

  • @property 装饰器是 property() 内置函数的一种 “语法糖” 形式,它让代码更简洁、更易读。
  • 使用 @property 可以将类内部的私有(按约定)变量封装起来,通过公共属性来控制其访问、修改和删除,实现了数据保护和校验。
  • 一个属性不一定需要所有 3 个方法(getter、setter、deleter)。例如,如果只定义 @property 而没有 @setter,则表示该属性是只读的。

Python @property 摘要

使用频率
官方文档 查看
相关函数 @classmethod@staticmethod

Python @property 示例

接下来,我们通过几个简单的例子来讲解一下 Python @property 是如何使用的。

示例 1:@property 创建只读属性

class Circle:
    def __init__(self, radius):
        # _radius 是内部私有变量,按约定加下划线
        # 通常不希望外部直接访问或修改它
        self._radius = radius

    @property
    def radius(self):
        # 圆的半径 (只读属性)
        return self._radius

# 创建 Circle 对象
c = Circle(5)

# 访问只读属性
print(c.radius)

# 尝试修改只读属性
try:
    c.radius = 10
except AttributeError as e:
    print(e)

运行结果如下:

5
property 'radius' of 'Circle' object has no setter

分析:

在 Circle 类中,radius 属性只定义了 @property 装饰的 getter 方法。此时我们可以像访问普通属性一样 c.radius 来获取它的值。

但如果我们尝试来修改它的值,Python 就会抛出 AttributeError 异常,这是因为它没有定义相应的 setter 方法。

示例 2:@property 创建读写属性(数据校验)

class Student:
    def __init__(self, name, score):
        self._name = name   # 私有属性
        self._score = 0     # 初始设为 0,通过 setter 验证

        # 调用 setter 来初始化 score,确保验证逻辑被执行
        self.score = score

    @property
    def name(self):
        # 学生的姓名
        return self._name

    @property
    def score(self):
        # 学生的成绩,读写属性,写入时进行验证
        return self._score

    @score.setter
    def score(self, value):
        if not isinstance(value, (int, float)):
            raise TypeError('成绩必须是数字!')
        if not (0 <= value <= 100):
            raise ValueError('成绩必须在 0 到 100 之间!')
        self._score = value

# 创建 Student 对象
s = Student('阿莫', 88)
print(f'姓名: {s.name}, 成绩: {s.score}')

# 修改成绩 (正常情况)
s.score = 95
print(f'姓名: {s.name}, 成绩: {s.score}')

# 尝试设置无效成绩 (触发校验错误)
try:
    s.score = 105
except ValueError as e:
    print(e)

运行结果如下:

姓名: 阿莫, 成绩: 88
姓名: 阿莫, 成绩: 95
成绩必须在 0100 之间!

分析:

在这个例子中,score 属性通过 @property 定义了 getter,并通过 @score.setter 定义了 setter。

在 setter 中,我们添加了数据类型和范围的验证。这使得对 score 属性的任何赋值操作都会经过这些校验,从而确保了数据的有效性,从而增强了类的健壮性。同时,外部仍然可以像访问普通属性一样读写 score,而无需关心底层的校验逻辑。

示例 3:@property 实现计算属性

class Rectangle:
    def __init__(self, width, height):
        # 使用 setter 来初始化,确保校验逻辑被执行
        self.width = width
        self.height = height

    @property
    def width(self):
        return self._width

    @width.setter
    def width(self, value):
        if value <= 0:
            raise ValueError('宽度必须为正数!')
        self._width = value

    @property
    def height(self):
        return self._height

    @height.setter
    def height(self, value):
        if value <= 0:
            raise ValueError('高度必须为正数!')
        self._height = value

    @property
    def area(self):
        # 计算矩形的面积,这是一个计算属性,每次访问都会重新计算
        return self._width * self._height

    @property
    def perimeter(self):
        # 计算矩形的周长。这是一个计算属性,每次访问都会重新计算
        return 2 * (self._width + self._height)

# 创建 Rectangle 对象
r = Rectangle(10, 5)

# 访问计算属性
print(f'矩形宽度: {r.width}, 高度: {r.height}')
print(f'矩形面积: {r.area}')         # 每次访问都会重新计算
print(f'矩形周长: {r.perimeter}')    # 每次访问都会重新计算

# 修改基础属性后,计算属性自动更新
r.width = 12
r.height = 6
print(f'矩形宽度: {r.width}, 高度: {r.height}')
print(f'矩形面积: {r.area}')           # 面积自动更新
print(f'矩形周长: {r.perimeter}')      # 周长自动更新

运行结果如下:

矩形宽度: 10, 高度: 5
矩形面积: 50
矩形周长: 30

矩形宽度: 12, 高度: 6
矩形面积: 72
矩形周长: 36

分析:

@property 可以用于创建一个计算属性。所谓的 “计算属性”,指的是该属性的值是根据其他属性动态计算出来的,而不是直接存储的。

在这个例子中,area 和 perimeter 都是计算属性。它们的值不是直接存储在对象中的,而是在每次访问时根据 _width 和 _height 动态计算出来的。这避免了数据冗余,并确保了数据的实时一致性。当我们修改 width 或 height 时,area 和 perimeter 会自动反映最新的计算结果。

上一篇: @staticmethod

下一篇: Python 内置函数

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号