inheritancePython字典以覆盖__setitem__

我正在build立一个子类dict ,并覆盖__setitem__ 。 我想确定我的方法将在所有可能设置字典项目的情况下被调用。

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

  1. 在构造函数中
  2. setdefault方法中
  3. update方法中

作为一个非常简单的testing:

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

UPDATE

根据gs的build议,我已经尝试了inheritanceUserDict(实际上,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) 

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

更新2

Peter Hansen的build议让我更仔细地看了一下dictobject.c,我意识到update方法可以简化一下,因为内置的字典构造函数总是调用内置的update方法。 现在看起来像这样:

 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] 

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

在阅读并重新阅读Python 2.6.4源代码(主要是Objects/dictobject.c ,但是我在其他地方grepped以查看使用各种方法的地方),我的理解是,下面的代码足以让我的__setitem__被调用对象被改变的时间,以及其他行为完全像Python字典:

Peter Hansen的build议让我更仔细地看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] 

我用这个代码testing了它:

 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) 

并通过。 我试过的所有其他实现在某个时候失败了。 我仍然会接受任何答案,告诉我我已经错过了一些东西,但是除此之外,我在几天之内勾选了这个答案,并将其称为正确答案:)

什么是你的用例子类字典?

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

完成你之后的最好的方法可能是MutableMapping抽象基类。 PEP 3119 – 介绍抽象基类

这也可以帮助你提出这个问题:“还有其他什么方法需要重写?”。 您将需要重写所有的抽象方法。 对于MutableMapping:抽象方法包括setitemdelitem 。 具体方法包括stream行,popitem,清​​除,更新。

我发现伊恩的回答和评论非常有帮助和清楚。 我只想指出,也许第一次调用超类__init__方法可能是安全的,当不需要时:我最近需要实现一个自定义的OrderedDict (我正在使用Python 2.7):根据实现和修改我的代码到build议的MyUpdateDict实现,我发现通过简单的replace

 class MyUpdateDict(dict): 

有:

 from collections import OrderedDict class MyUpdateDict(OrderedDict): 

那么上面发布的testing代码失败:

 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代码 ,事实certificateOrderedDict 需要调用__init__方法来初始化和设置必要的自定义属性。

因此,只需在super __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) 

我们有一个更通用的解决scheme,显然适用于字典和OrderedDict。

我不能说这个解决scheme通常是否有效,因为我只用OrderedDict来testing它。 但是,在尝试扩展其他字典子类时,调用super __init__方法可能无害或必要而不是有害的

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