使用__getitem__魔法函数实现多层嵌套字典dict

主题内容不是很好描述,就通过下面的例子来说明下我们经常遇到KeyError报错.  

In [15]: data = {"blog":"xiaorui.cc"}

In [16]: data['blog']
Out[16]: 'xiaorui.cc'

In [17]: data['at']
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-17-9652e590cd98> in <module>()
----> 1 data['at']

KeyError: 'at'

In [18]: data['at']['sign'] = 'github.com'
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-18-8909e479937d> in <module>()
----> 1 data['at']['sign'] = 'github.com'

KeyError: 'at'

文章写的不是很严谨,欢迎来喷,另外该文后续有更新的,请到原文地址查看更新。

http://xiaorui.cc/2016/01/31/%E4%BD%BF%E7%94%A8__getitem__%E9%AD%94%E6%B3%95%E5%87%BD%E6%95%B0%E5%AE%9E%E7%8E%B0%E5%A4%9A%E5%B1%82%E5%B5%8C%E5%A5%97%E5%AD%97%E5%85%B8dict/

对, 就是这个使用python字典时常常遇到的KeyError问题…    当你要实现一个比较复杂的字典,含有各种嵌套层,比如 xiaorui[‘a’][‘b’][‘c’] = “ok”, 要往c写值前要确认a b是否有,要不然会报错的.  除了写入数据之外,读取字典同样会遇到这类似的问题,get只能获取最近一层key,不能获取嵌套的字典key。  

那么怎么解决? 难度我们要一层层的判断字典?  

我们可以设计一个继承了dict的类,实现的方法也很是简单,只要使用__getitem__魔法函数就可以模拟友好的字典。     

#blog: xiaorui.cc

class MagicDict(dict):
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

a = MagicDict()

a[1][2][3] = 4
a[2][2]['test'] = 6
print 'end'
print a[1]
for i in a:
    print i

python2.7的collections有个defaultdict类,他的实现跟上面类似,多加了一些废话方法而已.

#blog: xiaorui.cc

In [2]: from collections import defaultdict


In [3]: a = defaultdict(dict)


In [4]: a['hell']['good']= 'xiaorui.cc'


In [5]: a

Out[5]: defaultdict(<type 'dict'>, {'hell': {'good': 'xiaorui.cc'}})

#########################################
__missing__
In [15]: d={}
In [16]: d.setdefault('rui', {}).setdefault('woca', {})['nima']=3

老规矩 ! 下面我们来瞅瞅collections defaultdict的源码. 

class defaultdict(dict):
    def __init__(self, default_factory=None, *args, **kwargs):
        if (default_factory is not None and
            not hasattr(default_factory, '__call__')):
            raise TypeError(b_('First argument must be callable'))
        dict.__init__(self, *args, **kwargs)
        self.default_factory = default_factory

    #当我们获取key时触发的函数.
    def __getitem__(self, key):
        try:
            return dict.__getitem__(self, key)
        except KeyError:
            return self.__missing__(key)
    
    #当我无法找到嵌套的key时会触发, 需要注意的是这里的__missing__跟method call的那个不一样. 
    def __missing__(self, key):
        if self.default_factory is None:
            raise KeyError(key)
        self[key] = value = self.default_factory()
        return value

    def __reduce__(self):
        if self.default_factory is None:
            args = tuple()
        else:
            args = self.default_factory,
        return type(self), args, None, None, self.iteritems()

    def copy(self):
        return self.__copy__()

    def __copy__(self):
        return type(self)(self.default_factory, self)

    def __deepcopy__(self, memo):
        import copy
        return type(self)(self.default_factory,
                          copy.deepcopy(self.items()))
    def __repr__(self):
        if isinstance(self.default_factory, types.MethodType) \
                and self.default_factory.im_self is not None \
                and issubclass(self.default_factory.im_class, defaultdict):
            defrepr = '<bound method sub._factory of defaultdict(...'
        else:
            defrepr = repr(self.default_factory)
        return 'defaultdict(%s, %s)' % (defrepr, dict.__repr__(self))

__all__ = ('defaultdict',)

对于数据格式很是混乱, 很是蛋疼的时候还是挺适合用这种友好的字典模式,如果对于数据格式的需求比较认真,那还是用普通的字典吧.     就比如为什么有很多人喜欢nosql里的mongodb,因为他的bson格式很是随意,但是这样会代码业务bug的产生…   当然还是看人,看业务…

END.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

发表评论

电子邮件地址不会被公开。 必填项已用*标注