子类化 Python 字典以覆盖 __setitem__ [英] Subclassing Python dictionary to override __setitem__

查看:28
本文介绍了子类化 Python 字典以覆盖 __setitem__的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在构建一个类,该类继承了 dict,并覆盖了 __setitem__.我想确定在所有可能设置字典项的情况下都会调用我的方法.

I am building a class which subclasses dict, and overrides __setitem__. I would like to be certain that my method will be called in all instances where dictionary items could possibly be set.

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

I have discovered three situations where Python (in this case, 2.6.4) does not call my overridden __setitem__ method when setting values, and instead calls PyDict_SetItem directly

  1. 在构造函数中
  2. setdefault方法中
  3. update方法中
  1. In the constructor
  2. In the setdefault method
  3. In the update method

作为一个非常简单的测试:

As a very simple test:

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__ 方法,我不得不重新实现这三个方法,如下所示:

You can see that the overridden method is only called when setting the items explicitly. To get Python to always call my __setitem__ method, I have had to reimplement those three methods, like this:

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__ 方法?

Are there any other methods which I need to override, in order to know that Python will always call my __setitem__ method?

更新

根据 gs 的建议,我试过像这样子类化 UserDict(实际上是 IterableUserDict,因为我想遍历键):

Per gs's suggestion, I've tried subclassing UserDict (actually, IterableUserDict, since I want to iterate over the keys) like this:

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)

这个类似乎在 setdefault 上正确调用了我的 __setitem__,但它没有在 update 上调用它,或者当初始数据为提供给构造函数.

This class seems to correctly call my __setitem__ on setdefault, but it doesn't call it on update, or when initial data is provided to the constructor.

更新 2

Peter Hansen 的建议让我更仔细地查看 dictobject.c,我意识到 update 方法可以简化一点,因为内置字典构造函数无论如何都只是简单地调用内置 update 方法.现在看起来像这样:

Peter Hansen's suggestion got me to look more carefully at dictobject.c, and I realised that the update method could be simplified a bit, since the built-in dictionary constructor simply calls the built-in update method anyway. It now looks like this:

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__.

I'm answering my own question, since I eventually decided that I really do want to subclass Dict, rather than creating a new mapping class, and UserDict still defers to the underlying Dict object in some cases, rather than using the provided __setitem__.

在阅读并重新阅读 Python 2.6.4 源代码(主要是 Objects/dictobject.c,但我在其他地方寻找各种方法使用的地方)后,我的理解是以下代码 足以让我的 __setitem__ 每次更改对象时都被调用,并且在其他方​​面的行为与 Python Dict 完全相同:

After reading and re-reading the Python 2.6.4 source (mostly Objects/dictobject.c, but I grepped eveywhere else to see where the various methods are used,) my understanding is that the following code is sufficient to have my __setitem__ called every time that the object is changed, and to otherwise behave exactly as a Python Dict:

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

Peter Hansen's suggestion got me to look more carefully at dictobject.c, and I realised that the update method in my original answer could be simplified a bit, since the built-in dictionary constructor simply calls the built-in update method anyway. So the second update in my answer has been added to the code below (by some helpful person ;-).

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]

我已经用这个代码测试过:

I've tested it with this code:

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)

然后就过去了.我尝试过的所有其他实现都在某些时候失败了.我仍然会接受任何表明我遗漏了什么的答案,但除此之外,我会在几天内勾选此旁边的复选标记,并称其为正确答案:)

and it passes. All other implementations I've tried have failed at some point. I'll still accept any answers that show me that I've missed something, but otherwise, I'm ticking the checkmark beside this one in a couple of days, and calling it the right answer :)

这篇关于子类化 Python 字典以覆盖 __setitem__的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆