字典对象相加

SO上看到一个比较好的问题.大概是:

For example I have two dicts:

Dict A:{'a':1,'b':2,'c':3}

Dict B:{'b':3,'c':4,'d':5}

I need a pythonic way of 'combining' two dicts such that the result is :

{'a':1,'b':5,'c':7,'d':5}

That is to say: if a key appears in both dicts, add their values, if it appears in only one dict, keep its value.

 

我发现有3类答案,每一个都有值得学习的地方.

(1)首先来一个比较酷的解法:

a = {'a': 'foo', 'b':'bar', 'c': 'baz'}
b = {'a': 'spam', 'c':'ham', 'x': 'blah'}

r = dict(a.items() + b.items() +
    [(k, a[k] + b[k]) for k in set(b) & set(a)])

这里面用到了一个难以觉察到的设置,举例说明:

>>> dict([(1,2),(1,2),(1,3),(2,3),(2,4)])
{1: 3, 2: 4}

可以看到,在将列表转换为字典的过程中,如果出现了相同的键,那么Python是默认取最后一个键及其对应的值.

另外,set的用法也显得非常简练.作者后来还给出了更普遍的版本:

def combine_dicts(a, b, op=None):
    op = op or (lambda x, y: x + y)
    return dict(a.items() + b.items() +
        [(k, op(a[k], b[k])) for k in set(b) & set(a)])

我才知道OR运算符不是仅仅返回True或False的...

>>> a= None or 2
>>> a
2
>>> 4 or 2
4
>>> not None or 2
True
>>>

这位作者曾经解答过我一个遍历树的问题,我花了一天时间冥思苦想,最后在递归和生成器的理解上进步一大截.

这是他SO主页:http://stackoverflow.com/users/989121/thg435

 

(2)下面来一个面向对象风格的:

class MyDict(dict):
    def __add__(self, oth):
        r = self.copy()
        for key, val in oth.items():
            if key in r:
                r[key] += val # You can custom it here
            else:
                r[key] = val
        return r

a = MyDict({'a':1, 'b':2, 'c':3})
b = MyDict({'b':3, 'c':4, 'd':5})

print a+b # Output {'a':1, 'b': 5, 'c': 7, 'd': 5}

原来__add__方法是这么来的.同时我也觉得,面向对象编程还是挺不错的,继承,撰写特殊方法,很方便的嘛.

另外,字典的copy方法原来就是在这种地方起作用的--很方便地返回一个和原字典内容一样的新的字典对象.毕竟2个字典相加后返回的新字典一般都是要求具有独立性的.

以前我一直想不通这个方法是拿来干嘛的..现在是明白一些了.

 

题外话:出于对copy一贯的警觉,我对字典的copy方法进行了一些测试,发现还是一贯地"陷阱".并非那么独立..

>>> d={1: [], 2: 4}
>>> e=d.copy()
>>> e
{1: [], 2: 4}
>>> e[1].append('trap?')
>>> d
{1: ['trap?'], 2: 4}

可见,当字典的值是可变对象时(比如list),copy是返回一个引用,并非新对象.总之,如果你的字典值包含可变对象,为了确保不发生"惊喜",还是用copy.deepcopy吧:

>>> import copy
>>> e=copy.deepcopy(d)
>>> e
{1: ['trap?'], 2: 4}
>>> e[1].append('no_trap')
>>> d
{1: ['trap?'], 2: 4}
>>> 

 

(3)最后是典型的Python风格--找现成轮子,import搞定之:

>>> from collections import Counter
>>> A = Counter({'a':1, 'b':2, 'c':3})
>>> B = Counter({'b':3, 'c':4, 'd':5})
>>> A + B
Counter({'c': 7, 'b': 5, 'd': 5, 'a': 1})

 

目前发现Python有以下一些基础库,提供一些不牵扯具体应用的有用的函数:

collections
functools
operator
itertools

 

 

posted @ 2013-10-28 20:42  LisPythoniC  阅读(692)  评论(0编辑  收藏  举报