如何创建易用的Python包装类以追踪变量当前与历史值?
如何创建几乎原生体验的可追踪变量包装类?
你遇到的问题非常典型——手动实现所有特殊方法不仅繁琐,还容易遗漏反向运算符这类细节。下面有几个更优雅的方案,可以让你的包装类几乎和原生变量一样好用:
方案1:用__getattr__代理属性+批量生成反向运算符
这个方法能大幅减少样板代码,同时解决右操作数的问题:
改进后的代码
class Updatable: def __init__(self, value=None, prev_value=None): self._value = value self._prev_value = prev_value @property def value(self): return self._value @property def prev_value(self): return self._prev_value def update(self, new_value): self._prev_value = self._value self._value = new_value def has_value(self): return self._value is not None def has_prev_value(self): return self._prev_value is not None # 自动代理所有未定义的属性/方法到被包装的value def __getattr__(self, name): return getattr(self._value, name) # 对象表示方法 def __str__(self): return str(self._value) def __repr__(self): return repr(self._value) # 迭代相关基础方法(如果value是可迭代对象) def __len__(self): return len(self._value) def __getitem__(self, key): return self._value[key] def __reversed__(self): return reversed(self._value) # 批量生成反向运算符(解决3 + a这类右操作数场景) def add_reverse_operators(cls): # 映射正向运算符到反向运算符 op_pairs = [ ("__add__", "__radd__"), ("__sub__", "__rsub__"), ("__mul__", "__rmul__"), ("__matmul__", "__rmatmul__"), ("__truediv__", "__rtruediv__"), ("__floordiv__", "__rfloordiv__"), ("__mod__", "__rmod__"), ("__pow__", "__rpow__"), ("__lshift__", "__rlshift__"), ("__rshift__", "__rrshift__"), ("__and__", "__rand__"), ("__xor__", "__rxor__"), ("__or__", "__ror__") ] for forward_op, reverse_op in op_pairs: def create_reverse_method(op): def method(self, other): return getattr(other, op)(self._value) return method setattr(cls, reverse_op, create_reverse_method(forward_op)) return cls # 给Updatable类添加反向运算符 Updatable = add_reverse_operators(Updatable)
为什么这管用?
__getattr__会自动处理所有你没手动定义的属性和方法,比如如果你的value是列表,my_updatable.append(1)会直接调用列表的append方法,不用自己写代理。- 批量生成的反向运算符解决了
3 + a这类场景——当Python找不到int的__add__能处理Updatable时,会调用Updatable的__radd__,我们在这里把操作转发给int的__add__,用self._value作为参数。
方案2:针对特定容器类型,继承UserList/UserDict/UserString
如果你的变量主要是列表、字典、字符串这类标准容器,直接继承collections里的这些类是最省心的——它们已经帮你实现了所有原生操作和反向运算符,你只需要添加历史值追踪的逻辑:
from collections import UserList class UpdatableList(UserList): def __init__(self, initlist=None): super().__init__(initlist) self._prev_value = None def update(self, new_value): # 保存当前值的副本作为历史值 self._prev_value = self.copy() self.data = new_value @property def prev_value(self): return self._prev_value def has_prev_value(self): return self._prev_value is not None
这样,你可以直接用UpdatableList做所有列表能做的事:
my_list = UpdatableList([1,2,3]) print(my_list + [4,5]) # [1,2,3,4,5] print([4,5] + my_list) # 反向操作也正常 my_list.update([6,7,8]) print(my_list.prev_value) # [1,2,3]
方案3:用描述符追踪类属性(适合类级别的变量追踪)
如果你的需求是追踪类属性的变化,而不是包装单个变量,描述符是更合适的选择:
class TrackedAttribute: def __init__(self, attr_name): self.attr_name = attr_name self.prev_attr_name = f"_prev_{attr_name}" def __get__(self, instance, owner): return instance.__dict__.get(self.attr_name) def __set__(self, instance, new_value): # 保存旧值 instance.__dict__[self.prev_attr_name] = instance.__dict__.get(self.attr_name) instance.__dict__[self.attr_name] = new_value def get_previous(self, instance): return instance.__dict__.get(self.prev_attr_name) # 使用示例 class MyAppState: current_user = TrackedAttribute("current_user") page_count = TrackedAttribute("page_count") state = MyAppState() state.current_user = "Alice" state.current_user = "Bob" print(state.current_user) # Bob print(state.current_user.get_previous(state)) # Alice
总结选择建议
- 通用场景(包装任意类型):选方案1,兼顾灵活性和代码简洁性。
- 特定容器类型:选方案2,直接利用Python内置的封装,最省心。
- 类属性追踪:选方案3,更符合面向对象的设计思路。
内容的提问来源于stack exchange,提问作者HelloGoodbye
相关产品推荐
相关产品推荐

