@property 装饰器简介
在 Python 中,@property 装饰器是一种优雅的属性管理工具,它允许你将类的方法伪装成属性(即无需使用 () 调用),同时可以在属性访问时添加逻辑(如数据校验、动态计算等)。
@property 的核心作用
- 隐藏实现细节:对外暴露属性式的访问接口,内部可封装复杂逻辑。
- 控制属性访问:在读取、设置或删除属性时触发自定义逻辑(如数据校验)。
- 兼容性:允许在不破坏现有代码的前提下修改内部实现。
基本用法:定义属性的读写删除
定义属性的读取(getter)
使用 @property 装饰器将一个方法转换为“属性读取”方法:
class Person: def __init__(self, name): self._name = name # 实际存储的属性(通常用下划线命名) @property def name(self): print("Getting name") return self._name # 使用 p = Person("Alice") print(p.name) # 输出: Getting name → Alice
定义属性的设置(setter)
使用 @属性名.setter 装饰器定义属性的设置逻辑:
class Person: def __init__(self, name): self._name = name @property def name(self): return self._name @name.setter def name(self, value): if not isinstance(value, str): raise ValueError("Name must be a string!") print("Setting name") self._name = value # 使用 p = Person("Alice") p.name = "Bob" # 正常设置 p.name = 123 # 抛出 ValueError
定义属性的删除(deleter)
使用 @属性名.deleter 装饰器定义删除逻辑:
class Person: def __init__(self, name): self._name = name @property def name(self): return self._name @name.deleter def name(self): print("Deleting name") del self._name # 使用 p = Person("Alice") del p.name # 输出: Deleting name
典型应用场景
数据校验
禁止非法值被设置:
class Circle: def __init__(self, radius): self.radius = radius # 通过 setter 设置 @property def radius(self): return self._radius @radius.setter def radius(self, value): if value <= 0: raise ValueError("Radius must be positive") self._radius = value
动态计算属性
属性值由其他属性动态计算得出:
class Rectangle: def __init__(self, width, height): self.width = width self.height = height @property def area(self): return self.width * self.height # 每次访问时计算 # 使用 rect = Rectangle(3, 4) print(rect.area) # 输出: 12 rect.width = 5 print(rect.area) # 输出: 20 (自动更新)
属性访问控制
限制某些属性为只读:
class User: def __init__(self, user_id): self._user_id = user_id @property def user_id(self): return self._user_id # 无 setter,无法修改 # 使用 user = User(1001) user.user_id = 2002 # 抛出 AttributeError: can't set attribute
深入原理
- 属性本质:@property创建了一个 property 对象,管理属性的 get、set、delete 方法。
- 描述符协议:property实现了描述符协议,通过 __get__、__set__ 和 __delete__ 方法拦截属性操作。
注意事项
- 避免副作用:Getter 方法尽量不修改对象状态。
- 性能考量:频繁计算的属性可考虑缓存结果。
- 命名冲突:属性名不要与实例变量同名(如用_name 存储数据,name 作为属性)。