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

Mic*_*ior 75 python dictionary

我正在调试一些代码,我想知道何时访问特定的字典.好吧,它实际上是一个子类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)
Run Code Online (Sandbox Code Playgroud)

' name_label'是一个最终将设置的键,我想用它来识别输出.然后我改变了我正在修改的类DictWatch而不是dict更改了对超级构造函数的调用.但是,似乎没有任何事情发生.我以为我很聪明,但我想知道我是否应该走另一个方向.

谢谢您的帮助!

Mat*_*son 68

子类化的另一个问题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
Run Code Online (Sandbox Code Playgroud)

  • 如果您使用的是Python 3,则需要更改此示例,以便`print`是`print()`函数,`update()`方法使用`items()`而不是`iteritems()`. (5认同)

Bra*_*ore 32

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

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

  • 真正.对于OP:为了将来参考,您可以简单地执行log.info('%s%s%s',a,b,c),而不是Python字符串格式化运算符. (3认同)

mak*_*puf 9

这不应该真正改变结果(对于良好的日志记录阈值,这应该是有效的):你的init应该是:

def __init__(self,*args,**kwargs) : dict.__init__(self,*args,**kwargs) 
Run Code Online (Sandbox Code Playgroud)

相反,因为如果用DictWatch([(1,2),(2,3)])或DictWatch(a = 1,b = 2)调用你的方法,这将失败.

(或者,更好的是,不要为此定义构造函数)


and*_*ate 9

考虑子类化UserDictUserList.这些类旨在被子类化,而正常dictlist不包含优化,并且包含优化.

  • 作为参考,Python 3.6中的[documentation](https://docs.python.org/3.6/library/collections.html?highlight=userdict#collections.UserDict)表示“该类的需求已被部分替代。可以直接从dict继承子类;但是,可以更轻松地使用此类,因为基础字典可以作为属性来访问”。 (5认同)
  • @andrew 一个例子可能会有所帮助。 (2认同)
  • @VasanthaGaneshK https://treyhunner.com/2019/04/why-you-shouldnt-inherit-from-list-and-dict-in-python/ (2认同)

Con*_*tor 9

正如安德鲁·佩特(Andrew Pate)的回答所提出的那样,子类化collections.UserDict而不是dict更不容易出错。

\n

这是一个显示天真继承时出现问题的示例dict

\n
class MyDict(dict):\n\n  def __setitem__(self, key, value):\n    super().__setitem__(key, value * 10)\n\n\nd = MyDict(a=1, b=2)  # Bad! MyDict.__setitem__ not called\nd.update(c=3)  # Bad! MyDict.__setitem__ not called\nd[\'d\'] = 4  # Good!\nprint(d)  # {\'a\': 1, \'b\': 2, \'c\': 3, \'d\': 40}\n
Run Code Online (Sandbox Code Playgroud)\n

UserDict继承自collections.abc.MutableMapping,因此这按预期工作:

\n
class MyDict(collections.UserDict):\n\n  def __setitem__(self, key, value):\n    super().__setitem__(key, value * 10)\n\n\nd = MyDict(a=1, b=2)  # Good: MyDict.__setitem__ correctly called\nd.update(c=3)  # Good: MyDict.__setitem__ correctly called\nd[\'d\'] = 4  # Good\nprint(d)  # {\'a\': 10, \'b\': 20, \'c\': 30, \'d\': 40}\n
Run Code Online (Sandbox Code Playgroud)\n

同样,你只需要实现__getitem__自动兼容key in my_dict, my_dict.get, \xe2\x80\xa6

\n

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

\n