上一篇文章主要说明了property() 函数的基本语法相关内容,这次将说明高级用法以及注意事项。
一、高级用法
1. 属性依赖关系
class Rectangle: def __init__(self, width=None, height=None, area=None): # 允许用不同方式初始化 if width is not None and height is not None: self._width = width self._height = height elif area is not None and width is not None: self._width = width self._height = area / width elif area is not None and height is not None: self._height = height self._width = area / height else: raise ValueError("需要提供足够的参数") @property def width(self): return self._width @width.setter def width(self, value): if value <= 0: raise ValueError("宽度必须大于0") self._width = value @property def height(self): return self._height @height.setter def height(self, value): if value <= 0: raise ValueError("高度必须大于0") self._height = value @property def area(self): return self._width * self._height @area.setter def area(self, value): if value <= 0: raise ValueError("面积必须大于0") # 保持宽度不变,调整高度 self._height = value / self._width @property def perimeter(self): return 2 * (self._width + self._height) @property def is_square(self): return self._width == self._height# 使用# 方式1: 通过宽高创建rect1 = Rectangle(width=4, height=5)print(f"矩形1 - 宽: {rect1.width}, 高: {rect1.height}, 面积: {rect1.area}")# 方式2: 通过宽和面积创建rect2 = Rectangle(width=4, area=20)print(f"矩形2 - 宽: {rect2.width}, 高: {rect2.height}, 面积: {rect2.area}")# 修改面积,自动调整高度rect1.area = 30print(f"修改面积后 - 宽: {rect1.width}, 高: {rect1.height}, 面积: {rect1.area}")
2. 使用property实现观察者模式
class ObservableProperty: """可观察属性""" def __init__(self, default=None): self.default = default self._value = default self._observers = [] def add_observer(self, callback): self._observers.append(callback) def remove_observer(self, callback): self._observers.remove(callback) def notify_observers(self, old_value, new_value): for observer in self._observers: observer(old_value, new_value) def __get__(self, obj, objtype): if obj is None: return self return self._value def __set__(self, obj, value): old_value = self._value self._value = value self.notify_observers(old_value, value) def __delete__(self, obj): self._value = self.defaultclass UserSettings: theme = ObservableProperty("light") language = ObservableProperty("zh-CN") font_size = ObservableProperty(14) def __init__(self): # 添加属性变化观察者 self.theme.add_observer(self._on_theme_change) self.language.add_observer(self._on_language_change) def _on_theme_change(self, old_value, new_value): print(f"主题从 {old_value} 更改为 {new_value}") def _on_language_change(self, old_value, new_value): print(f"语言从 {old_value} 更改为 {new_value}")# 使用settings = UserSettings()print(f"当前主题: {settings.theme}")print(f"当前语言: {settings.language}")settings.theme = "dark" # 触发通知settings.language = "en" # 触发通知
3. 属性版本控制
class VersionedProperty: """带版本控制的属性""" def __init__(self, default=None): self.default = default self._value = default self._versions = [] def __get__(self, obj, objtype): if obj is None: return self return self._value def __set__(self, obj, value): # 记录版本历史 self._versions.append({ 'timestamp': '当前时间', # 实际应用中应该用datetime.now() 'old_value': self._value, 'new_value': value }) # 最多保留10个版本 if len(self._versions) > 10: self._versions.pop(0) self._value = value @property def history(self): """获取版本历史""" return self._versions.copy() def revert(self, steps=1): """回滚到之前的版本""" if steps >= len(self._versions): steps = len(self._versions) - 1 for _ in range(steps): self._versions.pop() if self._versions: self._value = self._versions[-1]['new_value'] else: self._value = self.defaultclass Document: title = VersionedProperty("未命名文档") content = VersionedProperty("") def show_history(self, attr_name): """显示属性历史""" prop = getattr(type(self), attr_name) print(f"{attr_name} 历史:") for i, version in enumerate(prop.history, 1): print(f" 版本{i}: {version['old_value']} -> {version['new_value']}")# 使用doc = Document()doc.title = "第一版"doc.title = "第二版"doc.title = "第三版"doc.show_history('title')print(f"当前标题: {doc.title}")# 回滚type(doc).title.revert(1)print(f"回滚后标题: {doc.title}")
二、property与描述符的关系
# property实际上是一个描述符print(type(property)) # <class 'type'># 自定义简化版的propertyclass MyProperty: def __init__(self, fget=None, fset=None, fdel=None, doc=None): self.fget = fget self.fset = fset self.fdel = fdel self.__doc__ = doc def __get__(self, obj, objtype=None): if obj is None: return self if self.fget is None: raise AttributeError("不可读") return self.fget(obj) def __set__(self, obj, value): if self.fset is None: raise AttributeError("不可写") self.fset(obj, value) def __delete__(self, obj): if self.fdel is None: raise AttributeError("不可删除") self.fdel(obj) def getter(self, fget): return type(self)(fget, self.fset, self.fdel, self.__doc__) def setter(self, fset): return type(self)(self.fget, fset, self.fdel, self.__doc__) def deleter(self, fdel): return type(self)(self.fget, self.fset, fdel, self.__doc__)class Demo: def __init__(self, x): self._x = x @MyProperty def x(self): return self._x @x.setter def x(self, value): self._x = value# 使用自定义propertyobj = Demo(10)print(obj.x) # 输出: 10obj.x = 20print(obj.x) # 输出: 20
三、最佳实践和注意事项
1. 何时使用property
class GoodUseCase: """适合使用property的场景""" def __init__(self, data): self._data = data @property def processed_data(self): """需要计算的属性""" return self._data.upper() @property def is_valid(self): """布尔状态属性""" return len(self._data) > 0class BadUseCase: """不适合使用property的场景""" def __init__(self): self._counter = 0 @property def increment(self): """有副作用的方法不应该用property""" self._counter += 1 return self._counter # 应该改成普通方法 def increment_counter(self): self._counter += 1 return self._counter
2. 性能考虑
import timeclass PerformanceTest: def __init__(self, value): self._value = value @property def value(self): # 复杂的计算 time.sleep(0.001) # 模拟耗时操作 return self._value * 2# 在循环中频繁访问property可能影响性能obj = PerformanceTest(10)start = time.time()for _ in range(1000): _ = obj.valueprint(f"property访问: {time.time() - start:.4f}秒")# 缓存结果可以提高性能start = time.time()cached_value = obj.valuefor _ in range(1000): _ = cached_valueprint(f"缓存访问: {time.time() - start:.4f}秒")
总结
最佳实践:
使用@property装饰器语法(更简洁)
为复杂属性添加文档字符串
避免在property中做复杂计算
考虑使用缓存提高性能
合理使用只读属性