跟踪实例属性的更改。Python
我想实现一个函数,这个函数可以接收任何对象作为参数,并跟踪某个特定属性的值变化。然后,它会把这个属性的旧值保存在一个叫做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 个回答
你可以在运行时用自定义属性替换掉所有的属性。不过,你到底想实现什么呢?也许完全使用不可变类型会是个更好的选择?
这是一个开始:
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_
更不常见的前缀,因为你实际上是在尝试创建一个完全独立的命名空间。