如何使一个派生类,记录所有访问其成员?



我试图使一个类的行为像一个字典,除了任何时候它的一个方法被调用或它的一个属性被访问的事实记录。我要澄清我的意思通过展示天真实现我做(重复的代码被替换为省略号):

class logdict(dict):
def __init__(self, *args, **kwargs):
self._log = [
{'name': '__init__',
'args': tuple(map(repr, args)),
'kwargs': dict((key, repr(kwargs[key])) for key in kwargs)
}
]
return super().__init__(*args, **kwargs)
def __getitem__(self, key):
self._log.append({
'name': '__getitem__',
'args': (repr(key),),
'kwargs': {}
})
return super().__getitem__(key)
def __setitem__(self, key, value):
...
def __delitem__(self, key):
...
def __getattribute__(self, name):
if name == '_log': #avoiding infinite recursion
return super().__getattribute__(name)
...
def __contains__(self, key):
...
def logrepr(self):
log = ''
for logitem in self._log: #this is just formatting, nothing interesting here
log += '{fun}({rargs}{optsep}{rkwargs})n'.format(
fun = logitem['name'],
rargs = ', '.join(logitem['args']),
optsep = ', ' if len(logitem['kwargs'])>0 else '',
rkwargs = ', '.join('{} = {}'.format(key, logitem['kwargs'][key])
for key in logitem['kwargs'])
)
return log

在这里,至少对于我重载的方法,我保存了正在调用的方法及其参数的报告(如果我只是保存了参数,我就有可能看到最新的"版本";的可变对象(而不是旧对象)。这个实现是可行的:

d = logdict()
d['1'] = 3
d['1'] += .5
print('1' in d)
print('log:')
print(d.logrepr())

生产:

True
log:
__init__()
__setitem__('1', 3)
__getitem__('1')
__setitem__('1', 3.5)
__contains__('1')
__getattribute__('logrepr')

然而,它相当笨拙,我不确定我是否涵盖了所有可能的方法。是否有一种更有效的方法来做到这一点,理想情况下可以泛化到任何给定的类(并且包装和记录所有的方法,而不仅仅是可见的方法)?

注意:这不是这个问题的重复,因为它的问题是如何避免无限递归,而不是如何自动化/简化编写派生类的过程。

您可以自动生成dict的所有方法(除了一些例外),然后您不必重复这么多:

from functools import wraps

class LogDict(dict):
logs = {}
def _make_wrapper(name):
@wraps(getattr(dict, name))
def wrapper(self, *args, **kwargs):
LogDict.logs.setdefault(id(self), []).append((name, args, kwargs))
return getattr(super(), name)(*args, **kwargs)
return wrapper
for attr in dir(dict):
if callable(getattr(dict, attr)):
if attr in ("fromkeys", "__new__"):  # "classmethod-y"
continue
locals()[attr] = _make_wrapper(attr)
def logrepr(self):
return "".join(
"{fun}({rargs}{optsep}{rkwargs})n".format(
fun=fun,
rargs=", ".join(repr(arg) for arg in args),
optsep=", " if kwargs else "",
rkwargs=", ".join(
"{} = {}".format(key, value) for key, value in kwargs.items()
),
)
for fun, args, kwargs in LogDict.logs[id(self)]
)

d = LogDict()
d["1"] = 3
d["1"] += 0.5
print("1" in d)
print("log:")
print(d.logrepr())

打印的结果与你的解决方案相同。

在我的版本中,我还将日志存储在类对象上,然后我可以避免__getattribute__欺骗。

我会选择不同的方法。

我已经创建了一个简单的装饰类,叫做EventLogger。现在,您的LogDict将继承这个类,因此事件日志将成为LogDict的一部分。如果你想记录事件,你可以简单地用@EventLogger.log修饰你想要跟踪的方法。

如果需要,可以用其他日志功能扩展这个EventLogger。如果在某些方法中想要跟踪其他细节,例如运行时间,或将数据记录到其他日志,您可以轻松完成。

from functools import wraps

class EventLogger:
_logged_events = list()
@property
def logged_events(self):
return self._logged_events
def log(func):
@wraps(func)
def wrapped(self, *args, **kwargs):
self.__to_logger(self, func_name=func.__name__, *args, **kwargs)
return func(self, *args, **kwargs)
return wrapped
def __to_logger(self, *args, **kwargs):
func_name = kwargs.pop('func_name')
args = args[1:]  # first param is self
# TODO: implement the logging format
self._logged_events.append(
dict(func=func_name,
args=args,
kwargs=kwargs)
)

class LogDict(dict, EventLogger):
@EventLogger.log
def __init__(self, *args, **kwargs):
return super().__init__(*args, **kwargs)
@EventLogger.log
def __setitem__(self, key, value):
return super().__setitem__(key, value)
@EventLogger.log
def __getitem__(self, key):
return super().__getitem__(key)

ld = LogDict(a=10)
ld['aa'] = 5
print(ld)
print(ld.logged_events)

最新更新