如何跟踪Python中列表和字典的变化?
我有一个类,这个类的实例需要跟踪它的属性变化。
举个例子:当你写 obj.att = 2
的时候,这种变化很容易追踪,只需要重写 __setattr__
方法就可以了。
但是,当我想改变的属性是一个对象,比如列表或字典时,就出现了问题。
我该如何跟踪像 obj.att.append(1)
或 obj.att.pop(2)
这样的操作呢?
我在考虑扩展列表或字典的类,但在 obj
和 obj.att
都初始化后,对这些类的实例进行“猴子补丁”,这样当调用 .append
时 obj
能收到通知。不过,这样做让我觉得不太优雅。
我想到的另一种方法是把 obj
的实例传入列表的初始化,但这样会破坏很多现有的代码,而且似乎比之前的方法还要不优雅。
还有其他的想法或建议吗?我是不是漏掉了什么简单的解决方案?
5 个回答
4
与其直接修改已有的代码,不如创建一个代理类:
- 先做一个代理类,让它继承字典、列表或集合等类型
- 在设置属性的时候,拦截这个过程,如果值是字典、列表或集合,就把它包装成代理类
- 在代理类的
__getattribute__
方法中,确保调用的是被包装的类型的方法,但在调用之前要做好跟踪
优点:
- 不需要改动原来的类
缺点:
- 你只能使用你知道和预期的几种类型
5
你可以利用集合模块中的抽象基类,字典(dict)和列表(list)就是实现了这些基类。这让你可以使用标准库提供的接口来编写代码,只需要重写一小部分方法,比如 __getitem__
、__setitem__
、__delitem__
和 insert
。然后在 __getattribute__
里面把属性包裹在一个可追踪的适配器中。
import collections
class Trackable(object):
def __getattribute__(self, name):
attr = object.__getattribute__(self, name)
if isinstance(attr, collections.MutableSequence):
attr = TrackableSequence(self, attr)
if isinstance(attr, collections.MutableMapping):
attr = TrackableMapping(self, attr)
return attr
def __setattr__(self, name, value):
object.__setattr__(self, name, value)
# add change tracking
class TrackableSequence(collections.MutableSequence):
def __init__(self, tracker, trackee):
self.tracker = tracker
self.trackee = trackee
# override all MutableSequence's abstract methods
# override the the mutator abstract methods to include change tracking
class TrackableMapping(collections.MutableMapping):
def __init__(self, tracker, trackee):
self.tracker = tracker
self.trackee = trackee
# override all MutableMapping's abstract methods
# override the the mutator abstract methods to include change tracking
13
我看到这个问题时很好奇怎么解决,于是想了一个办法。虽然这个办法没有我想象中那么简单,但可能会对你有帮助。首先,看看它的效果:
class Tracker(object):
def __init__(self):
self.lst = trackable_type('lst', self, list)
self.dct = trackable_type('dct', self, dict)
self.revisions = {'lst': [], 'dct': []}
>>> obj = Tracker() # create an instance of Tracker
>>> obj.lst.append(1) # make some changes to list attribute
>>> obj.lst.extend([2, 3])
>>> obj.lst.pop()
3
>>> obj.dct['a'] = 5 # make some changes to dict attribute
>>> obj.dct.update({'b': 3})
>>> del obj.dct['a']
>>> obj.revisions # check out revision history
{'lst': [[1], [1, 2, 3], [1, 2]], 'dct': [{'a': 5}, {'a': 5, 'b': 3}, {'b': 3}]}
接下来是实现这个效果的 trackable_type()
函数:
def trackable_type(name, obj, base):
def func_logger(func):
def wrapped(self, *args, **kwargs):
before = base(self)
result = func(self, *args, **kwargs)
after = base(self)
if before != after:
obj.revisions[name].append(after)
return result
return wrapped
methods = (type(list.append), type(list.__setitem__))
skip = set(['__iter__', '__len__', '__getattribute__'])
class TrackableMeta(type):
def __new__(cls, name, bases, dct):
for attr in dir(base):
if attr not in skip:
func = getattr(base, attr)
if isinstance(func, methods):
dct[attr] = func_logger(func)
return type.__new__(cls, name, bases, dct)
class TrackableObject(base):
__metaclass__ = TrackableMeta
return TrackableObject()
这个函数基本上是用一个元类来覆盖对象的每个方法,以便在对象发生变化时记录一些修改日志。这个方法的测试不是特别全面,我也只尝试了 list
和 dict
这两种对象类型,但对于这两种类型来说,效果还不错。