子类化Python字典以覆盖__setitem__

时间:2010-01-13 23:01:58

标签: python dictionary subclass

我正在构建一个子类dict的类,并覆盖__setitem__。我想确定我的方法将在可能设置字典项的所有实例中被调用。

我发现三种情况,Python(在本例中为2.6.4)在设置值时不会调用我重写的__setitem__方法,而是直接调用PyDict_SetItem

  1. 在构造函数中
  2. setdefault方法
  3. update方法
  4. 作为一个非常简单的测试:

    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]
    

4 个答案:

答案 0 :(得分:47)

我正在回答我自己的问题,因为我最终决定我真的想要继承Dict,而不是创建一个新的映射类,而UserDict仍然推迟到底层的Dict对象案例,而不是使用提供的__setitem__

阅读并重新阅读Python 2.6.4源代码(主要是Objects/dictobject.c之后,但我在其他地方找到了其他方法以查看使用各种方法的位置),我的理解是以下代码是< / em>足以在每次更改对象时调用__setitem__,并且在其他方​​面完全像Python Dict一样:

Peter Hansen的建议让我更仔细地看dictobject.c,我意识到原始答案中的更新方法可以简化一点,因为内置字典构造函数只是调用内置更新方法无论如何。所以我的答案中的第二次更新已添加到下面的代码中(由一些有用的人; - )。

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)

它过去了。我尝试过的所有其他实现在某些方面都失败了。我仍然会接受任何答案,告诉我我错过了什么,但除此之外,我在几天之内勾选了这个旁边的复选标记,并称之为正确答案:)

答案 1 :(得分:4)

你对dict进行子类化的用例是什么?

您不需要这样做来实现类似dict的对象,在您的情况下编写普通类可能更简单,然后添加对dict接口所需子集的支持。

实现目标的最佳方法可能是MutableMapping抽象基类。 PEP 3119 -- Introducing Abstract Base Classes

这也可以帮助您解答“我还需要覆盖其他任何方法吗?”。您将需要覆盖所有抽象方法。对于MutableMapping:抽象方法包括 setitem delitem 。具体方法包括pop,popitem,clear,update。

答案 2 :(得分:3)

我发现伊恩的答案和评论非常有帮助和明确。我只想指出,在没有必要时,第一次调用超类__init__方法可能更安全:我最近需要实现自定义OrderedDict(我正在使用Python 2.7):根据提议的MyUpdateDict实现实现和修改我的代码后,我发现只需替换

class MyUpdateDict(dict):

使用:

from collections import OrderedDict
class MyUpdateDict(OrderedDict):

然后上面发布的测试代码失败了:

Traceback (most recent call last):
File "Desktop/test_updates.py", line 52, in <module>
    my_dict = MyUpdateDict([('b',2),('c',3)],a=1)
File "Desktop/test_updates.py", line 5, in __init__
    self.update(*args, **kwargs)
File "Desktop/test_updates.py", line 18, in update
    self[key] = other[key]
File "Desktop/test_updates.py", line 9, in __setitem__
    super(MyUpdateDict, self).__setitem__(key, value)
File "/usr/lib/python2.7/collections.py", line 59, in __setitem__
    root = self.__root
AttributeError: 'MyUpdateDict' object has no attribute '_OrderedDict__root'

查看collections.py code,事实证明OrderedDict 需要调用其__init__方法,以便初始化和设置必要的自定义属性。

因此,只需添加第一次调用超级__init__方法,

from collections import OrderedDict
class MyUpdateDict(Orderedict):
def __init__(self, *args, **kwargs):
    super(MyUpdateDict, self).__init__() #<-- HERE call to super __init__
    self.update(*args, **kwargs)

我们有一个更通用的解决方案,显然适用于dict和OrderedDict。

我无法说明此解决方案是否通常有效,因为我仅使用OrderedDict测试它。但是,当尝试扩展其他dict子类时,调用super __init__方法可能是无害的或必要的而不是有害的

答案 3 :(得分:0)

使用object.keyname = value而不是object [“keyname”] = value