如何正确地子类化dict并重写__getitem__和__setitem__

104 投票
6 回答
126219 浏览
提问于 2025-04-15 20:04

我正在调试一些代码,想知道什么时候会访问一个特定的字典。其实,这个字典是一个类,它是从 dict 这个内置字典类派生出来的,并且增加了一些额外的功能。总之,我想自己也从 dict 继承一个类,并重写 __getitem____setitem__ 方法,以便输出一些调试信息。目前,我有了以下代码:

class DictWatch(dict):
    def __init__(self, *args):
        dict.__init__(self, args)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        log.info("GET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        return val

    def __setitem__(self, key, val):
        log.info("SET %s['%s'] = %s" % str(dict.get(self, 'name_label')), str(key), str(val)))
        dict.__setitem__(self, key, val)

'name_label' 是一个键,最终会被设置,我想用它来标识输出。我已经把我正在修改的类改成了从 DictWatch 继承,而不是 dict,并且修改了调用父类构造函数的部分。不过,现在似乎没有任何反应。我原以为我很聪明,但我在想是不是应该换个方向来做。

6 个回答

26

可以考虑去扩展一下 UserDict 或者 UserList 这两个类。它们是专门设计用来被继承的,而普通的 dictlist 则不是这样,并且这两个类还进行了优化。

89

当你在创建一个新的字典类(也就是从dict继承)时,有一个问题需要注意:内置的__init__方法不会自动调用update,而内置的update方法又不会调用__setitem__。这意味着,如果你希望所有的设置操作都通过你自己定义的__setitem__函数来处理,你需要自己确保这个函数被调用:

class DictWatch(dict):
    def __init__(self, *args, **kwargs):
        self.update(*args, **kwargs)

    def __getitem__(self, key):
        val = dict.__getitem__(self, key)
        print('GET', key)
        return val

    def __setitem__(self, key, val):
        print('SET', key, val)
        dict.__setitem__(self, key, val)

    def __repr__(self):
        dictrepr = dict.__repr__(self)
        return '%s(%s)' % (type(self).__name__, dictrepr)
        
    def update(self, *args, **kwargs):
        print('update', args, kwargs)
        for k, v in dict(*args, **kwargs).items():
            self[k] = v
45

你现在做的事情应该是完全可以的。我测试了你的类,除了你的日志语句里缺少一个开括号之外,其他都没问题。我能想到的只有两点。首先,你的日志语句输出设置正确了吗?你可能需要在脚本的开头加上 logging.basicConfig(level=logging.DEBUG)

其次,__getitem____setitem__ 只有在使用 [] 访问时才会被调用。所以确保你是通过 d[key] 来访问 DictWatch,而不是用 d.get()d.set()

撰写回答