Python中如何获取两个字典之间的差异?

160

我有两个字典,我需要找到它们之间的差异,这应该给我一个键和一个值。

我搜索并找到了一些类似datadiff和dictdiff-master的插件/包,但是当我尝试在Python 2.7中导入它们时,它说没有定义这样的模块。

我在这里使用了一个集合:

first_dict = {}
second_dict = {}
 
value = set(second_dict) - set(first_dict)
print value

我的输出是:

>>> set(['SCD-3547', 'SCD-3456'])

我只得到键,但我也需要获取值。


4
如果键相同但值不同,您是否还需要找到差异? - Tim Pietzcker
20个回答

212

我认为最好使用集合的对称差运算来实现这个功能。这里是文档链接

>>> dict1 = {1:'donkey', 2:'chicken', 3:'dog'}
>>> dict2 = {1:'donkey', 2:'chimpansee', 4:'chicken'}
>>> set1 = set(dict1.items())
>>> set2 = set(dict2.items())
>>> set1 ^ set2
{(2, 'chimpansee'), (4, 'chicken'), (2, 'chicken'), (3, 'dog')}

它是对称的,因为:

>>> set2 ^ set1
{(2, 'chimpansee'), (4, 'chicken'), (2, 'chicken'), (3, 'dog')}

使用差异运算符时并非如此。

>>> set1 - set2
{(2, 'chicken'), (3, 'dog')}
>>> set2 - set1
{(2, 'chimpansee'), (4, 'chicken')}

然而,将结果集转换为字典可能不是一个好主意,因为您可能会丢失信息:

>>> dict(set1 ^ set2)
{2: 'chicken', 3: 'dog', 4: 'chicken'}

5
很好,这基本上是九年前雷蒙德·赫廷格在另一个论坛上建议的相同解决方案:http://code.activestate.com/recipes/576644-diff-two-dictionaries/#c1 - cscanlin
46
优雅的解决方案。但是它不能应用于具有不可哈希值的字典。 - Craynic Cai
15
类型错误:无法哈希化的类型:'dict'。 - Lei Yang
28
@LeiYang,你收到TypeError: unhashable type: 'dict'的错误提示是因为你的“顶层字典”中有一个值是另一个字典。这个提议的解决方案只适用于扁平的字典。 - josebama
1
所有使用 set(...) 的解决方案都无法处理字典键或值中存在的不可哈希类型。 - Levko Ivanchuk
显示剩余7条评论

114

尝试使用字典推导式来运行下面的代码段:

```python ```
value = { k : second_dict[k] for k in set(second_dict) - set(first_dict) }
在上面的代码中,我们找到键的差异,然后重新构建一个字典来获取相应的值。

12
由于dictset都是哈希表,我不明白为什么dict不能支持difference()方法,尤其是set可以支持。 - Ray
7
这只是为第二个字典中存在但第一个字典中不存在的键提供了字典,那么第一个字典中存在但第二个字典中不存在的内容呢? - henryJack
14
您可以尝试以下类似的方法来比较数值:value = { k : second_dict[k] for k, _ in set(second_dict.items()) - set(first_dict.items()) }使用dict.items()可以得到从键到值的元组,然后在集合差异中进行比较。因此,这将提供所有新的键以及更改的值。 - ascrookes
7
我遇到了“TypeError: unhashable type: 'dict'”错误。 - wander95
2
我认为你不需要将字典键转换为集合,你可以直接在键上使用集合操作,像这样:second_dict.keys() - first_dict.keys() - user3064538
显示剩余9条评论

83
另一个解决方案是使用dictdifferhttps://github.com/inveniosoftware/dictdiffer)。
import dictdiffer                                          

a_dict = {                                                 
  'a': 'foo',
  'b': 'bar',
  'd': 'barfoo'
}                                                          

b_dict = {                                                 
  'a': 'foo',                                              
  'b': 'BAR',
  'c': 'foobar'
}                                                          

for diff in list(dictdiffer.diff(a_dict, b_dict)):         
    print(diff)

一个diff是一个元组,包含了变化的类型、变化的值以及条目的路径。
('change', 'b', ('bar', 'BAR'))
('add', '', [('c', 'foobar')])
('remove', '', [('d', 'barfoo')])

3
调试最实用的解决方案。 - ijoseph
1
其他解决方案无法解决我的问题,因为我的字典中还嵌套了一个字典。这个解决方案可以处理它。谢谢! - Joel

20

你可以使用DeepDiff

pip install deepdiff

除了其他功能外,它还使您可以递归计算字典、可迭代对象、字符串和其他对象的差异:

>>> from deepdiff import DeepDiff

>>> d1 = {1:1, 2:2, 3:3, "foo":4}
>>> d2 = {1:1, 2:4, 3:3, "bar":5, 6:6}
>>> DeepDiff(d1, d2)
{'dictionary_item_added': [root['bar'], root[6]],
 'dictionary_item_removed': [root['foo']],
 'values_changed': {'root[2]': {'new_value': 4, 'old_value': 2}}}

它让您看到了发生了什么更改(包括类型),添加了什么,移除了什么。 它还可以让您做很多其他事情,例如忽略重复项和忽略路径(由正则表达式定义)。


14

1
这很酷,但在Python 2中不起作用:ValueError: no such test method in <class 'unittest.case.TestCase'>: runTest - Matt
19
如果您仍在使用Python 2,那么请接受我最深切的慰问! - Brainless

12

你考虑使用集合是正确的,我们只需要深入挖掘一下,就可以让你的方法生效。

首先,看一下示例代码:

test_1 = {"foo": "bar", "FOO": "BAR"}
test_2 = {"foo": "bar", "f00": "b@r"}

我们现在可以看到,这两个字典都包含一个相似的键/值对:

{"foo": "bar", ...}

每个字典还包含完全不同的键值对。但是我们如何检测差异?字典不支持该操作。相反,您需要使用集合。

以下是如何将每个字典转换为可用的集合:

set_1 = set(test_1.items())
set_2 = set(test_2.items())

这将返回一个包含一系列元组的集合。每个元组代表字典中的一个键值对。

现在,要找到set_1和set_2之间的差异:

print set_1 - set_2
>>> {('FOO', 'BAR')}

想要收回一个字典?很容易,只需:

dict(set_1 - set_2)
>>> {'FOO': 'BAR'}


10

我建议使用由优秀开发人员编写的已有代码,如pytest。它可以处理任何数据类型,不仅限于字典类型。顺便提一下,pytest在测试方面非常出色。

from _pytest.assertion.util import _compare_eq_any

print('\n'.join(_compare_eq_any({'a': 'b'}, {'aa': 'vv'}, verbose=3)))

输出结果为:

Left contains 1 more item:
{'a': 'b'}
Right contains 1 more item:
{'aa': 'vv'}
Full diff:
- {'aa': 'vv'}
?    -    ^^
+ {'a': 'b'}
?        ^

如果您不喜欢使用以_开头的私有函数,只需查看源代码并将函数复制/粘贴到您的代码中。

P.S.:已测试通过pytest==6.2.4


9
这是我的自己的版本,结合了https://dev59.com/CVwY5IYBdhLWcg3wRF9S#67263119https://dev59.com/CVwY5IYBdhLWcg3wRF9S#48544451,现在我发现它与https://dev59.com/CVwY5IYBdhLWcg3wRF9S#47433207非常相似。
def dict_diff(dict_a, dict_b, show_value_diff=True):
  result = {}
  result['added']   = {k: dict_b[k] for k in set(dict_b) - set(dict_a)}
  result['removed'] = {k: dict_a[k] for k in set(dict_a) - set(dict_b)}
  if show_value_diff:
    common_keys =  set(dict_a) & set(dict_b)
    result['value_diffs'] = {
      k:(dict_a[k], dict_b[k])
      for k in common_keys
      if dict_a[k] != dict_b[k]
    }
  return result

first_dict未定义。 - Crystal
谢谢您的注意,@Crystal,现在应该已经修复了。 - juandesant

8

在其他答案中提到的使用对称差集运算符的函数,可以保留值的来源:

def diff_dicts(a, b, missing=KeyError):
    """
    Find keys and values which differ from `a` to `b` as a dict.

    If a value differs from `a` to `b` then the value in the returned dict will
    be: `(a_value, b_value)`. If either is missing then the token from 
    `missing` will be used instead.

    :param a: The from dict
    :param b: The to dict
    :param missing: A token used to indicate the dict did not include this key
    :return: A dict of keys to tuples with the matching value from a and b
    """
    return {
        key: (a.get(key, missing), b.get(key, missing))
        for key in dict(
            set(a.items()) ^ set(b.items())
        ).keys()
    }

示例

print(diff_dicts({'a': 1, 'b': 1}, {'b': 2, 'c': 2}))

# {'c': (<class 'KeyError'>, 2), 'a': (1, <class 'KeyError'>), 'b': (1, 2)}

如何运作

我们使用对称差集合操作符在获取项目的元组生成的两个字典中生成一组不同的 (key, value) 元组。

然后,我们从中创建一个新的字典来将键合并在一起,并对这些进行迭代。这些是从一个字典到另一个字典唯一发生了变化的键。

然后,我们使用这些键组成一个新的字典,每个字典的值都用我们的缺失标记替换键不存在的情况。


2
这个很好用!但是当一个或两个字典包含列表时,它就无法工作:set(a.items()) ^ set(b.items()) TypeError: 不可哈希类型: 'list' - Qlii256

8

这个功能基于字典键提供所有差异(以及保持不变的内容)。它还突出了一些漂亮的字典推导、集合操作和 Python 3.6 类型注释 :)

from typing import Dict, Any, Tuple
def get_dict_diffs(a: Dict[str, Any], b: Dict[str, Any]) -> Tuple[Dict[str, Any], Dict[str, Any], Dict[str, Any], Dict[str, Any]]:

    added_to_b_dict: Dict[str, Any] = {k: b[k] for k in set(b) - set(a)}
    removed_from_a_dict: Dict[str, Any] = {k: a[k] for k in set(a) - set(b)}
    common_dict_a: Dict[str, Any] = {k: a[k] for k in set(a) & set(b)}
    common_dict_b: Dict[str, Any] = {k: b[k] for k in set(a) & set(b)}
    return added_to_b_dict, removed_from_a_dict, common_dict_a, common_dict_b

如果您想比较字典中的
values_in_b_not_a_dict = {k : b[k] for k, _ in set(b.items()) - set(a.items())}

common_dict_a和common_dict_b不是一样的吗?A和B共有的内容就是一组键值对,不需要重复。 - codingknob
键是相同的,但值可能不同。这就是为什么它在那里的原因。 - juandesant

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