Python@property装饰器全解析

Written by

in

文章目录
  • 假设你有一个类,直接暴露公共属性: 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 等逻辑。
  • 最简单的用法是将一个方法变成“只读属性”: 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 就是一个只读属性:可以获取,不能直接赋值。
  • 常见场景是既要读,也要写,并在写入时做检查或处理: 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 的函数里。
  • 有时你希望通过 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,避免调用自己。
  • 保持接口稳定一开始可以简单写成公有属性: 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 里做懒加载等。
  • 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装饰器内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客! 您可能感兴趣的文章: python装饰器中@property属性的使用解析 Python深入分析@property装饰器的应用 Python装饰器中@property使用详解 详解Python装饰器之@property Python @property装饰器原理解析 实例讲解Python编程中@property装饰器的用法 Python黑魔法@property装饰器的使用技巧解析 介绍Python的@property装饰器的用法
  • 目录
    • 一、为什么需要@property?
    • 二、@property的基本用法(只读属性)
    • 三、定义可读可写属性:@xxx.setter
    • 四、带有删除逻辑的属性:@xxx.deleter
    • 五、计算属性:不存储结果,只在访问时计算
    • 六、命名习惯:前有下划线的“内部属性”
    • 七、@property的优点总结
    • 八、与传统 getter/setter 的区别
    • 九、一些注意事项
    • 十、一个综合示例

    在 Python 中,@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 等逻辑。

    最简单的用法是将一个方法变成“只读属性”:

    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 就是一个只读属性:可以获取,不能直接赋值。

    常见场景是既要读,也要写,并在写入时做检查或处理:

    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 的函数里。

    有时你希望通过 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,避免调用自己。

    保持接口稳定
    一开始可以简单写成公有属性:

    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 里做懒加载等。

    • 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装饰器内容请搜索风君子博客以前的文章或继续浏览下面的相关文章希望大家以后多多支持风君子博客!

    您可能感兴趣的文章:

    • python装饰器中@property属性的使用解析
    • Python深入分析@property装饰器的应用
    • Python装饰器中@property使用详解
    • 详解Python装饰器之@property
    • Python @property装饰器原理解析
    • 实例讲解Python编程中@property装饰器的用法
    • Python黑魔法@property装饰器的使用技巧解析
    • 介绍Python的@property装饰器的用法

    站内搜索