如何正确地将dict子类化并覆盖__getitem__和__setitem__


84

我正在调试一些代码,我想找出何时访问特定词典。好吧,它实际上是一个子类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并更改了对超级构造函数的调用。不过,似乎什么都没有发生。我以为自己很聪明,但是我想知道我是否应该朝另一个方向发展。

谢谢您的帮助!


您是否尝试使用打印而不是日志?另外,您能否解释一下如何创建/配置日志?
pajton 2010年

2
dict.__init__*args吗?
汤姆·罗素

4
看起来有点像装饰的好人选。
汤姆·罗素

Answers:


39

您正在做的事情绝对应该有效。我测试了您的类,除了在日志语句中缺少左括号之外,它还可以正常工作。我只能想到两件事。首先,您的log语句的输出设置正确吗?您可能需要logging.basicConfig(level=logging.DEBUG)在脚本的顶部放置一个。

其次,__getitem____setitem__只在所谓的[]访问。因此,请确保您仅DictWatch通过d[key]而不是d.get()和访问d.set()


实际上,这不是多余的内容,而是周围缺少的开头内容(str(dict.get(self, 'name_label')), str(key), str(val)))

3
真正。给OP:供将来参考,您可以简单地执行log.info('%s%s%s',a,b,c),而不是Python字符串格式运算符。
BrainCore'3

日志记录级别最终成为问题。我正在调试其他人的代码,最初是在另一个文件中进行测试的,该文件具有不同级别的调试集。谢谢!
Michael Mior 2010年

73

子类化时的另一个问题dict是,内置__init__函数不会调用update,而内置update函数不会调用__setitem__。因此,如果您希望所有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).iteritems():
            self[k] = v

9
如果您使用的是Python 3,则需要更改此示例,以便printprint()函数和update()方法使用items()代替iteritems()
Al Sweigart

我已经尝试过您的解决方案,但似乎它仅适用于一个索引级别(即dict [key]而不是dict [key1] [key2] ...)*
Andrew Naguib

d [key1]返回的内容,可能是字典。第二个关键字对此进行了索引。除非返回的内容也支持监视行为,否则此技术将无法工作。
马特·安德森

1
@AndrewNaguib:为什么要使用嵌套数组?嵌套数组也不适用于普通的python字典(如果您自己未实现的话)
Igor Chubin,

1
@AndrewNaguib:__getitem__将需要进行测试,val并且仅在有条件的情况下进行测试,即if isinstance(val, dict): ...
martineau

14

考虑子类化UserDictUserList。这些类旨在被子类而正常dictlist都没有,和包含最佳化。


9
作为参考,Python 3.6中的文档说:“直接从dict继承子类的能力已部分取代了对此类的需要;但是,可以更轻松地使用此类,因为基础字典可作为属性来访问”。
肖恩

@andrew一个示例可能会有所帮助。
Vasantha Ganesh K


9

那并不能真正改变结果(对于良好的日志记录阈值,它应该可以工作):您的init应该是:

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

相反,因为如果使用DictWatch([(1,2 ,,(2,3)])或DictWatch(a = 1,b = 2)调用方法,则此操作将失败。

(或者,最好不要为此定义一个构造函数)


我只担心dict[key]访问的形式,所以这不是问题。
Michael Mior 2010年

1

您要做的就是

class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

供我个人使用的样本用法

### EXAMPLE
class BatchCollection(dict):
    def __init__(self, inpt={}):
        super(BatchCollection, self).__init__(inpt)

    def __setitem__(self, key, item):
        if (isinstance(key, tuple) and len(key) == 2
                and isinstance(item, collections.Iterable)):
            # self.__dict__[key] = item
            super(BatchCollection, self).__setitem__(key, item)
        else:
            raise Exception(
                "Valid key should be a tuple (database_name, table_name) "
                "and value should be iterable")

注意:仅在python3中测试


0

为了完成安德鲁·帕特的回答,以下示例显示dict和之间的区别UserDict

正确覆盖dict是很难的:

class MyDict(dict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called
d.update(c=3)  # Bad! MyDict.__setitem__ not called
d['d'] = 4  # Good!
print(d)  # {'a': 1, 'b': 2, 'c': 3, 'd': 40}

UserDict从继承collections.abc.MutableMapping,因此自定义要容易得多:

class MyDict(collections.UserDict):

  def __setitem__(self, key, value):
    super().__setitem__(key, value * 10)


d = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called
d.update(c=3)  # Good: MyDict.__setitem__ correctly called
d['d'] = 4  # Good
print(d)  # {'a': 10, 'b': 20, 'c': 30, 'd': 40}

同样,你只需要实现__getitem__自动与之兼容key in my_dictmy_dict.get...

注意:UserDict不是的子类dict,因此isinstance(UserDict(), dict)会失败(但isinstance(UserDict(), collections.abc.MutableMapping)会起作用)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.