我正在建立一个子类dict,并重写__setitem__的类。我想确定在可能设置字典项的所有实例中都会调用我的方法。

我发现了三种情况,其中Python(在本例中为2.6.4)在设置值时未调用覆盖的__setitem__方法,而是直接调用PyDict_SetItem

  • 在构造函数
  • setdefault方法中
  • update方法中

  • 作为一个非常简单的测试:
    class MyDict(dict):
        def __setitem__(self, key, value):
            print "Here"
            super(MyDict, self).__setitem__(key, str(value).upper())
    
    >>> a = MyDict(abc=123)
    >>> a['def'] = 234
    Here
    >>> a.update({'ghi': 345})
    >>> a.setdefault('jkl', 456)
    456
    >>> print a
    {'jkl': 456, 'abc': 123, 'ghi': 345, 'def': '234'}
    

    您可以看到仅在显式设置项目时才调用覆盖方法。为了使Python始终调用我的__setitem__方法,我必须重新实现这三个方法,如下所示:
    class MyUpdateDict(dict):
        def __init__(self, *args, **kwargs):
            self.update(*args, **kwargs)
    
        def __setitem__(self, key, value):
            print "Here"
            super(MyUpdateDict, self).__setitem__(key, value)
    
        def update(self, *args, **kwargs):
            if args:
                if len(args) > 1:
                    raise TypeError("update expected at most 1 arguments, got %d" % len(args))
                other = dict(args[0])
                for key in other:
                    self[key] = other[key]
            for key in kwargs:
                self[key] = kwargs[key]
    
        def setdefault(self, key, value=None):
            if key not in self:
                self[key] = value
            return self[key]
    

    为了知道Python将始终调用我的__setitem__方法,还有其他需要重写的方法吗?

    更新

    按照gs的建议,我尝试了子类化UserDict(实际上是IterableUserDict,因为我想遍历键)是这样的:
    from UserDict import *;
    class MyUserDict(IterableUserDict):
        def __init__(self, *args, **kwargs):
            UserDict.__init__(self,*args,**kwargs)
    
        def __setitem__(self, key, value):
            print "Here"
            UserDict.__setitem__(self,key, value)
    

    此类似乎在__setitem__上正确调用了我的setdefault,但在update上或向构造函数提供初始数据时并未调用它。

    更新2

    彼得·汉森(Peter Hansen)的建议使我更加仔细地研究dictobject.c,我意识到可以对更新方法进行一些简化,因为内置字典构造函数无论如何都只是调用内置更新方法。现在看起来像这样:
    def update(self, *args, **kwargs):
        if len(args) > 1:
            raise TypeError("update expected at most 1 arguments, got %d" % len(args))
        other = dict(*args, **kwargs)
        for key in other:
            self[key] = other[key]
    

    最佳答案

    我正在回答自己的问题,因为我最终决定我确实确实想对Dict进行子类化,而不是创建一个新的映射类,并且UserDict在某些情况下仍然遵循底层的Dict对象,而不是使用提供的__setitem__

    在阅读并重新阅读了Python 2.6.4的源代码之后(大多数是Objects/dictobject.c,但是我在其他地方grepped了一下,以了解在何处使用了各种方法,)我的理解是,以下代码足以在每次对象访问时调用__setitem__。被更改,并以其他方式完全像Python Dict:

    彼得·汉森(Peter Hansen)的建议使我更加仔细地研究dictobject.c,并且我意识到原始答案中的update方法可以简化一些,因为内置字典构造函数无论如何都只是调用内置update方法。因此,我的回答中的第二次更新已添加到下面的代码中(由一些乐于助人的人;-)。

    class MyUpdateDict(dict):
        def __init__(self, *args, **kwargs):
            self.update(*args, **kwargs)
    
        def __setitem__(self, key, value):
            # optional processing here
            super(MyUpdateDict, self).__setitem__(key, value)
    
        def update(self, *args, **kwargs):
            if args:
                if len(args) > 1:
                    raise TypeError("update expected at most 1 arguments, "
                                    "got %d" % len(args))
                other = dict(args[0])
                for key in other:
                    self[key] = other[key]
            for key in kwargs:
                self[key] = kwargs[key]
    
        def setdefault(self, key, value=None):
            if key not in self:
                self[key] = value
            return self[key]
    

    我已经用以下代码对其进行了测试:
    def test_updates(dictish):
        dictish['abc'] = 123
        dictish.update({'def': 234})
        dictish.update(red=1, blue=2)
        dictish.update([('orange', 3), ('green',4)])
        dictish.update({'hello': 'kitty'}, black='white')
        dictish.update({'yellow': 5}, yellow=6)
        dictish.setdefault('brown',7)
        dictish.setdefault('pink')
        try:
            dictish.update({'gold': 8}, [('purple', 9)], silver=10)
        except TypeError:
            pass
        else:
            raise RunTimeException("Error did not occur as planned")
    
    python_dict = dict([('b',2),('c',3)],a=1)
    test_updates(python_dict)
    
    my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
    test_updates(my_dict)
    

    它通过了。我尝试过的所有其他实现在某些时候都失败了。我仍然会接受任何表明我错过了某些东西的答案,但是否则,我将在几天之内在其旁边打勾,并称其为正确的答案:)

    10-07 12:29
    查看更多