在Python中合并两个字典,不允许重复的键。

6

这个问题不同于其他的字典合并问题,因为冲突的重复项应该失败或返回False。其他解决方案使用优先规则来决定如何管理当一个键可能映射到两个不同的变量时应该怎么做。

我如何在Python中高效地合并两个字典。例如,考虑以下内容:

d1 = {'x': 'a', 'y': 'b', 'z': 'c'}
d2 = {'z': 'c', 'w': 'r'}
d3 = {'z': 'd', 'w': 'r'}

所以,合并字典1和2的结果将会是:
{'x': 'a', 'y': 'b', 'z': 'c', 'w': 'r'}

但是将1和3或2和3合并应该失败,因为z存在冲突。

我的解决方案是:

def merge_dicts(d1,d2):
   k1=d1.keys()
   k2=d2.keys()
   unified_dict=dict()
   for k in k1:
       # look up in second dictionary
      if k in k2:
         pt=d2[k]  #pt stands for 'plain text'
         # if lookup is a contradiction, return empty dictionary
         #  don't even bother with partial results
         if pt!=d1[k]:
             return dict()
         else:
             unified_dict[k]=d1[k]  # safe: key is consistent
      else:
          unified_dict[k]=d1[k] # safe:  no key in k2

# get the rest
# already resolved intersection issues so just get set difference
   for k in d2.keys():
      if k not in d1.keys():
          unified_dict[k]=d2[k]

   return unified_dict

有任何改进吗?

5个回答

6

在此处使用字典视图,它们允许您将字典键视为集合:

def merge_dicts(d1, d2):
    try:
        # Python 2
        intersection = d1.viewkeys() & d2
    except AttributeError:
        intersection = d1.keys() & d2
       
    if any(d1[shared] != d2[shared] for shared in intersection):
        return {}  # empty result if there are conflicts

    # leave the rest to C code, execute a fast merge using dict()
    return dict(d1, **d2)

上面的代码只是测试共享键是否引用了不匹配的值;合并本身最好只留给dict()函数。
我使该函数在Python 2和Python 3上都能正常工作;如果您只需要支持其中一个,请删除try..except并将intersection替换为相关表达式。在Python 3中,dict.keys()方法默认返回字典视图。此外,在仅限于Python 3的代码中,我会使用{**d1, **d2}扩展,它更快、更干净,不仅限于字符串键。
您可以想象把它变成一行代码;Python 3版本:
def merge_dicts(d1, d2):
    return (
        {} if any(d1[k] != d2[k] for k in d1.keys() & d2)
        else {**d1, **d2}
    )

如果您只需要支持Python 3.9或更高版本,则可以使用“|”字典合并运算符来支持。请参考| dictionary merge operator
def merge_dicts(d1, d2):
   return (
       {} if any(d1[k] != d2[k] for k in d1.keys() & d2)
       else d1 | d2
   )

演示:

>>> d1 = {'x': 'a', 'y': 'b', 'z': 'c'}
>>> d2 = {'z': 'c', 'w': 'r'}
>>> d3 = {'z': 'd', 'w': 'r'}
>>> merge_dicts(d1, d2)
{'y': 'b', 'x': 'a', 'z': 'c', 'w': 'r'}
>>> merge_dicts(d1, d3)
{}
>>> merge_dicts(d2, d3)
{}

1
由于这个问题的目标是找到最Pythonic的方法,我感谢针对Python 2和3的答案。我需要在这里挖掘和学习的是dict(d1,**d2)语义。谢谢大家的帮助。 - Michael Tuchman
2
关于 dict(d1, **d2),请参阅Python参数中的双星号(*)和单星号()有什么作用?以及dict()函数文档dict(d1)仅创建d1的副本。dict(**d2)会以迂回的方式创建d2的副本。dict(d1, **d2)创建d1的副本,并添加d2的键值对。 - Martijn Pieters

3
d1 = {'x': 'a', 'y': 'b', 'z': 'c'}                                                             
d2 = {'z': 'c', 'w': 'r'}
d3 = {'z': 'd', 'w': 'r'}

def dict_merge(d1, d2):
    """docstring for merge"""
    # doesn't work with python 3.x. Use keys(), items() instead
    if len(d1.viewkeys() & d2) != len(d1.viewitems() & d2.viewitems()):
        return {}
    else:
        result = dict(d1, **d2)
        return result

if __name__ == '__main__':
    print dict_merge(d1, d2)

1
这会直接修改 d1,并将值限制为可哈希对象。 - Martijn Pieters
2
此外,您可以删除 .keys() 调用,因为这些调用完全是冗余和低效的。再加上字典视图已经是支持交集的集合对象,不需要那么多调用,您可以将其重新设计为更高效和更清晰的版本。 - Martijn Pieters
感谢Martijn的深入评论,我已经发布了一篇编辑稿。 - vHalaharvi
这绝对是最快的方法,感谢完全依赖于KeysViewItemsView容器上的基于C的集合操作。@MartijnPieters再次获胜... - Cecil Curry
@CecilCurry 是的,这就是为什么我在我的答案中也使用了它们。如果您只使用 Python 3.9 或更新版本,请使用 | 而不是 dict(d1. **d2),以获得更好的可读性、更快的速度和无需限制于仅字符串键! - Martijn Pieters

1

一个稍微不同的方法(预检查):

d1={'x':'a','y':'b','z':'c'}
d2={'z':'c','w':'r'}
d3={'z':'d','w':'r'}

def merge(d1, d2):
    for (k1,v1) in d1.items():
        if k1 in d2 and v1 != d2[k1]:
            raise ValueError
    ret = d1.copy()
    ret.update(d2)
    return ret

print(merge(d1,d2))
print(merge(d1,d3))

1
为什么不使用 set?
#!/usr/bin/python

d1={'x':'a','y':'b','z':'c'}
d2={'w':'r'}
d3={'z':'d','w':'r'}
d4={'x':'a','y':'b','z':'c'}

def merge_dicts(d1, d2):
    dicts = d1.items() + d2.items()
    if len(dicts) != len(set(dicts)):
        raise ValueError
    else:
        return dict(set(dicts))
print merge_dicts(d1, d2)
print merge_dicts(d1, d3)
try:
    print merge_dicts(d1, d4)
except:
    print "Failed"

$ python foo.py
{'y': 'b', 'x': 'a', 'z': 'c', 'w': 'r'}
{'y': 'b', 'x': 'a', 'z': 'd', 'w': 'r'}
Failed

编辑:

实际上,这种方法对于不可哈希的值是行不通的,而下面这个方法是可行的:

#!/usr/bin/python
# coding: utf-8 

#!/usr/bin/python

d1={'x':'a','y':'b','z':'c'}
d2={'w':'r'}
d3={'z':'d','w':'r'}
d4={'x':'a','y':'b','z':'c'}

def merge_dicts(d1, d2):
    merged= d1.copy()
    for k, v in d2.iteritems():
        if k in merged:
            raise ValueError
        else:
            merged[k] = v 
    return merged

for one, two in [(d1, d2), (d1, d3), (d1, d4)]:
    try:
        print merge_dicts(one, two)
    except:
        print "Merge Failed for %s with %s" %(one, two)

1
这需要是可哈希的。如果字典包含其他字典、列表或集合,就不能使用它。 - Martijn Pieters
缩进,第一个值必须是可哈希的。 - wilfriedroset
你的第二个版本再次禁止具有相等值的共享密钥。 - Martijn Pieters
我没有使用set,因为我不确定set操作是否会重复其他管理计算时间的工作。然而,我更喜欢使用set操作,因为它可以使代码更简洁和表达更清晰。 - Michael Tuchman

1
这是您想要的:

这样做:

def merge_dicts(d1, d2):
    # Join dicts and get rid of non-conflicting dups
    elems = set(d1.items()) | set(d2.items())

    # Construct join dict
    res = {}
    for k, v in elems:
        if k in res.keys():
            return dict()  # conflicting dup found
        res[k] = v;

    return res

3
这将限制您只能使用不可变的;您不能在这些字典中使用列表、集合或字典。 - Martijn Pieters
3
此外,在 Python 3 中,dict.items() 已经像集合一样。在 Python 2 中,您可以使用 dict.viewitems(),其附加优势是它像集合一样运行,而不必使值不可变。 - Martijn Pieters

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接