You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何创建可记录所有成员访问的Python派生类,适配任意基类

解决方案

核心思路是通过动态生成子类的方式,自动注入所有魔法方法的包装逻辑,无需手动逐个重载,同时支持适配任意基类。

实现代码

import functools

def LoggedClass(base_cls):
    # 要排除的特殊方法,避免递归或异常
    exclude_methods = {
        '__new__', '__init__', '__getattribute__', '__dict__', '__class__',
        '_log', 'logrepr'
    }

    # 通用方法包装器
    def wrapper(method_name, method):
        @functools.wraps(method)
        def wrapped(self, *args, **kwargs):
            # 记录调用信息,参数存repr避免可变对象后续修改影响记录
            self._log.append({
                'name': method_name,
                'args': tuple(map(repr, args)),
                'kwargs': {k: repr(v) for k, v in kwargs.items()}
            })
            return method(self, *args, **kwargs)
        return wrapped

    # 动态生成子类
    class _Logged(base_cls):
        def __init__(self, *args, **kwargs):
            self._log = [{
                'name': '__init__',
                'args': tuple(map(repr, args)),
                'kwargs': {k: repr(v) for k, v in kwargs.items()}
            }]
            super().__init__(*args, **kwargs)

        def __getattribute__(self, name):
            # 绕过日志相关属性和方法,避免无限递归
            if name in ('_log', 'logrepr'):
                return super().__getattribute__(name)
            # 记录属性访问
            self._log.append({
                'name': '__getattribute__',
                'args': (repr(name),),
                'kwargs': {}
            })
            return super().__getattribute__(name)

        def logrepr(self):
            log = ''
            for logitem in self._log:
                args_str = ', '.join(logitem['args'])
                kwargs_str = ', '.join(f'{k} = {v}' for k, v in logitem['kwargs'].items())
                sep = ', ' if args_str and kwargs_str else ''
                log += f"{logitem['name']}({args_str}{sep}{kwargs_str})\n"
            return log

    # 批量注入所有魔法方法的包装
    for method_name in dir(base_cls):
        if method_name.startswith('__') and method_name.endswith('__'):
            if method_name not in exclude_methods:
                method = getattr(base_cls, method_name)
                if callable(method):
                    setattr(_Logged, method_name, wrapper(method_name, method))

    return _Logged

使用示例

和你原有实现的使用方式完全一致,只需要把logdict替换为LoggedClass(dict)即可:

# 生成带日志的字典类
logdict = LoggedClass(dict)

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')

优势

  • 通用适配任意基类:如果需要给list、set等其他类加日志,只需要调用LoggedClass(list)、LoggedClass(set)即可,无需重复编写逻辑
  • 自动覆盖所有魔法方法:不需要手动逐个重载,自动收集基类所有可调用的魔法方法并注入日志逻辑
  • 保留原有特性:完全继承基类的所有功能,无侵入性修改

内容的提问来源于stack exchange,提问作者sortai

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 23:18:04