You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建易用的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 09:18:19