如何创建一个派生的 class 来记录对其成员的所有访问?
how to make a derived class that logs all access to its members?
我正在尝试制作一个行为类似于字典的 class,除了任何时候调用其方法之一或访问其属性之一时,都会记录该事实。
我将通过展示我所做的幼稚实现来阐明我的意思(重复代码替换为省略号):
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
在这里,至少对于我重载的方法,我正在保存正在调用的方法及其参数的 repr(如果我只保存参数,我 运行 有看到最新的风险可变对象的“版本”而不是旧对象)。
这个实现有点管用:
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')
但是它相当笨拙,我不确定我是否涵盖了所有可能的方法。
有没有更有效的方法来做到这一点,理想情况下可以推广到任何给定的 class(并且包装和记录所有 dunder 方法,而不仅仅是可见的方法)?
注意:这不是 this question 的副本,因为其中的问题是如何避免无限递归,而不是如何 automate/simplify 编写派生 class 的过程.
我会选择不同的方法。
我创建了简单的装饰器 class,名为 EventLogger
。现在您的 LogDict
将从这个 class 继承,因此事件日志将成为 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)
你可以自动生成 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())
这会打印与您的解决方案相同的内容。
在我的版本中,我还将日志存储在 class 对象上,这样我就可以避免 __getattribute__
欺骗。
我正在尝试制作一个行为类似于字典的 class,除了任何时候调用其方法之一或访问其属性之一时,都会记录该事实。 我将通过展示我所做的幼稚实现来阐明我的意思(重复代码替换为省略号):
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
在这里,至少对于我重载的方法,我正在保存正在调用的方法及其参数的 repr(如果我只保存参数,我 运行 有看到最新的风险可变对象的“版本”而不是旧对象)。 这个实现有点管用:
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')
但是它相当笨拙,我不确定我是否涵盖了所有可能的方法。 有没有更有效的方法来做到这一点,理想情况下可以推广到任何给定的 class(并且包装和记录所有 dunder 方法,而不仅仅是可见的方法)?
注意:这不是 this question 的副本,因为其中的问题是如何避免无限递归,而不是如何 automate/simplify 编写派生 class 的过程.
我会选择不同的方法。
我创建了简单的装饰器 class,名为 EventLogger
。现在您的 LogDict
将从这个 class 继承,因此事件日志将成为 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)
你可以自动生成 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())
这会打印与您的解决方案相同的内容。
在我的版本中,我还将日志存储在 class 对象上,这样我就可以避免 __getattribute__
欺骗。