我有一个类,其中此类的实例需要跟踪其属性的更改。
示例:obj.att = 2
只需覆盖__setattr__
的{{1}}即可轻松追踪。
但是,当我想要更改的属性是一个对象本身,如列表或字典时,会出现问题。
我如何跟踪obj
或obj.att.append(1)
?
我正在考虑扩展列表或字典类,但是在obj.att.pop(2)
和obj
都被初始化后,猴子修补这些类的实例,以便在obj.att
获得通知时调用obj
之类的东西。不知何故,这感觉不是很优雅。
我能想到的另一种方法是将.append
的实例传递给列表初始化,但这会破坏很多现有代码,而且它看起来甚至比以前的方法更优雅。
还有其他想法/建议吗?是否有一个我在这里缺少的简单解决方案?
答案 0 :(得分:7)
我很好奇当我看到这个问题时如何实现这一点,这是我提出的解决方案。并不像我希望的那样简单但它可能有用。首先,这是行为:
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
之外,我还没有尝试过任何其他对象类型,但它似乎对那些人没用。
答案 1 :(得分:4)
您可以利用集合模块中的抽象基类,该模块用于编写和列出工具。这为您提供了一个标准的库接口,可以使用一小段要覆盖的方法进行编码,__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
答案 2 :(得分:3)
您可以创建代理类,而不是猴子修补:
__getattribute__
中,确保在包装类型上调用该方法,但在执行此操作之前请先进行跟踪。临:
缺点:
答案 3 :(得分:0)
我的jsonfile模块检测到(嵌套的)JSON兼容Python对象的更改。只需将JSONFileRoot
子类化即可使更改检测适应您的需求。
>>> import jsonfile
>>> class Notify(jsonfile.JSONFileRoot):
... def on_change(self):
... print(f'notify: {self.data}')
...
>>> test = Notify()
>>> test.data = 1
notify: 1
>>> test.data = [1,2,3]
notify: [1, 2, 3]
>>> test.data[0] = 12
notify: [12, 2, 3]
>>> test.data[1] = {"a":"b"}
notify: [12, {'a': 'b'}, 3]
>>> test.data[1]["a"] = 20
notify: [12, {'a': 20}, 3]
请注意,它在不支持集的情况下以e-satis建议的方式继续使用代理类。
答案 4 :(得分:0)
您还可以包装要跟踪的字典或列表方法,并确保在包装器内执行所需的操作。这是一个字典示例:
from functools import wraps
def _setChanged(func):
@wraps(func)
def wrappedFunc(self, *args, **kwargs):
self.changed = True
return func(self, *args, **kwargs)
return wrappedFunc
def _trackObjectMethods(calssToTrack):
for methodName in dir(calssToTrack):
if methodName in calssToTrack._destructive:
setattr(calssToTrack, methodName, _setChanged(getattr(calssToTrack, methodName)))
class Dictionary(dict):
_destructive = ('__delitem__', '__setitem__', 'clear', 'pop', 'popitem', 'setdefault', 'update')
def __init__(self, *args, **kwargs):
self.changed = False
super().__init__(*args, **kwargs)
_trackObjectMethods(Dictionary)
d = Dictionary()
print(d.changed)
d["1"] = 'test'
print(d.changed)
d.changed = False
print(d.changed)
d["12"] = 'test2'
print(d.changed)
如您所见,如果词典中的任何项目发生了变化,那么我添加到自定义词典对象中的自定义变量将被设置为True。这样,我就可以知道自上次将更改后的变量设置为False以来对象是否有更改。