跟踪实例属性的更改。Python

2 投票
2 回答
2599 浏览
提问于 2025-04-16 21:44

我想实现一个函数,这个函数可以接收任何对象作为参数,并跟踪某个特定属性的值变化。然后,它会把这个属性的旧值保存在一个叫做old_name的属性里。

举个例子:

class MyObject(object):
     attr_one = None
     attr_two = 1

我们可以把这个神奇的函数命名为magic_function()

这样我就可以像这样使用它:

obj = MyObject()
obj = magic_function(obj)
obj.attr_one = 'new value'
obj.attr_two = 2

它会保存旧值,这样我就可以像这样获取旧值:

print obj.old_attr_one
None
print obj.attr_one 'new value'

还有

print obj.old_attr_two
1
print obj.attr_two
2

类似这样的功能……我想知道怎么能做到这一点,而不去修改实例的类?

2 个回答

0

你可以在运行时用自定义属性替换掉所有的属性。不过,你到底想实现什么呢?也许完全使用不可变类型会是个更好的选择?

4

这是一个开始:

class MagicWrapper(object):
    def __init__(self, wrapped):
        self._wrapped = wrapped

    def __getattr__(self, attr):
        return getattr(self._wrapped, attr)

    def __setattr__(self, attr, val):
        if attr == '_wrapped':
            super(MagicWrapper, self).__setattr__('_wrapped', val)
        else:
            setattr(self._wrapped, 'old_' + attr, getattr(self._wrapped, attr))
            setattr(self._wrapped, attr, val)


class MyObject(object):
    def __init__(self):
        self.attr_one = None
        self.attr_two = 1

obj = MyObject()
obj = MagicWrapper(obj)
obj.attr_one = 'new value'
obj.attr_two = 2

print obj.old_attr_one
print obj.attr_one
print obj.old_attr_two
print obj.attr_two

当你试图处理一些奇怪的对象时,这个方法并不是万无一失的(在Python中,几乎没有什么是万无一失的),但对于“正常”的类来说,它应该是有效的。你可以写很多代码来更接近完全复制被包装对象的行为,但要做到完美几乎是不可能的。这里最重要的是要知道,许多特殊方法不会被重定向到被包装的对象上。

如果你想在不以某种方式包装obj的情况下做到这一点,那就会变得很麻烦。这里有一个选项:

def add_old_setattr_to_class(cls):
    def __setattr__(self, attr, val):
        super_setattr = super(self.__class__, self).__setattr__
        if attr.startswith('old_'):
            super_setattr(attr, val)
        else:
            super_setattr('old_' + attr, getattr(self, attr))
            super_setattr(attr, val)
    cls.__setattr__ = __setattr__


class MyObject(object):
    def __init__(self):
        self.attr_one = None
        self.attr_two = 1

obj = MyObject()
add_old_setattr_to_class(obj.__class__)
obj.attr_one = 'new value'
obj.attr_two = 2

print obj.old_attr_one
print obj.attr_one
print obj.old_attr_two
print obj.attr_two

注意,如果你在外部提供的对象上使用这个方法,它会非常具有侵入性。它会全局修改你应用魔法的对象的,而不仅仅是那个实例。这是因为像其他一些特殊方法一样,__setattr__不会在实例的属性字典中查找;查找会直接跳到类上,所以没有办法仅在实例上重写__setattr__。如果我在实际使用中遇到这种代码,我会把它称为一种奇怪的黑客手法(当然,如果是我自己写的,那就是“聪明的巧思” ;))。

这个版本可能会或可能不会与已经对__setattr____getattr__/__getattribute__做过特殊处理的对象很好地配合。如果你多次修改同一个类,我认为这仍然有效,但你会得到越来越多的被包装的__setattr__定义。你可能应该尽量避免这种情况;也许可以在类上设置一个“秘密标志”,并在修改cls之前在add_old_setattr_to_class中检查它。你可能还应该使用一个比old_更不常见的前缀,因为你实际上是在尝试创建一个完全独立的命名空间。

撰写回答