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

查看:122
本文介绍了将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.

我发现了三种情况,在这种情况下, 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 / li>
  3. 更新方法

  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的建议, ve尝试子类化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 __ 但是它不会在更新上调用它,或者当向构造函数提供初始数据时。

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,我意识到更新方法可以简化一点,因为内置的字典构造函数只需调用内置的更新方法即可。现在看起来像这样:

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源代码(主要是对象/ dictobject.c ,但是我希望evey其他地方可以看到使用各种方法的位置,)我的理解是,以下代码足以使我的__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的建议让我仔细观察迪ctobject.c ,我意识到我的原始答案中的更新方法可以简化一点,因为内置的字典构造函数只是简单地调用内置的更新方法。所以我的答案的第二个更新已被添加到下面的代码(由一些有用的人; - )。

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]

我用这段代码测试了:

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天全站免登陆