【问题标题】:How to get the difference between two dictionaries in Python?如何在 Python 中获取两个字典之间的区别?
【发布时间】:2015-12-25 06:16:12
【问题描述】:

我有两个字典,我需要找出两者之间的区别,这应该给我一个键和一个值。

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

我在这里用了一套:

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

我的输出是:

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

我只得到键,我还需要得到值。

【问题讨论】:

  • 如果键相同但值不同,您是否还需要找出差异?

标签: python dictionary


【解决方案1】:

使用字典理解尝试以下 sn-p:

value = { k : second_dict[k] for k in set(second_dict) - set(first_dict) }

在上面的代码中,我们找到键的差异,然后重建一个dict,取对应的值。

【讨论】:

  • 既然dictset 都是hashmaps,我不知道为什么dict 不能支持difference() 方法,因为set 支持。
  • 这只是为您提供第二个字典中但不在第一个字典中的键的字典。那些在第一个但不在第二个的东西呢?
  • @henryJack 您可以执行以下操作来比较值:value = { k : second_dict[k] for k, _ in set(second_dict.items()) - set(first_dict.items()) } 执行dict.items() 提供从键到值的元组,并在集合差异中进行比较。因此,这将提供所有新键以及更改的值。
  • 我收到TypeError: unhashable type: 'dict'
  • 我认为你不需要将 dict 键转换为集合,你可以在键上使用集合操作,像这样:second_dict.keys() - first_dict.keys()
【解决方案2】:

您认为使用集合是正确的,我们只需要更深入地挖掘您的方法即可。

一、示例代码:

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'}

【讨论】:

  • 请注意这是不对称的,除了(set 1 - set 2) 之外,您还需要(set 2 - set 1)。否则,您将无法捕获所有差异,例如输出中缺少的 {("f00": "b@r")}
【解决方案3】:

我认为最好使用集合的对称差分运算来做到这一点Here is the link to the doc

>>> 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')}

使用difference operator 时并非如此。

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

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

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

【讨论】:

  • 太好了,这与 Raymond Hettinger 近 9 年前在另一个论坛上提出的解决方案大致相同:code.activestate.com/recipes/576644-diff-two-dictionaries/#c1
  • 优雅的解决方案。但它不能应用于具有不可哈希值的字典。
  • 我怀疑这个答案是 OP 真正需要的,应该是公认的答案。
  • TypeError: unhashable type: 'dict'
  • @LeiYang 你得到TypeError: unhashable type: 'dict' 因为你的“顶级字典”中的一个值是另一个字典。 此建议的解决方案仅适用于 flat 字典
【解决方案4】:

另一种解决方案是dictdiffer (https://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

差异是一个元组,包含更改类型、更改后的值和条目的路径。

('change', 'b', ('bar', 'BAR'))
('add', '', [('c', 'foobar')])
('remove', '', [('d', 'barfoo')])

【讨论】:

  • 最实用的调试方案。
  • 其他解决方案没有解决我的问题,因为我的 dict-in-dict 结构。这个解决方案可以处理它。谢谢!
【解决方案5】:

此功能仅根据字典键为您提供所有差异(以及保持不变的差异)。它还突出了一些不错的 Dict 理解、Set 操作和 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 的共同点是一组键:值对。无需复制。
  • 密钥相同;但值可能不同。这就是它存在的原因。
【解决方案6】:

这个呢?不那么漂亮,但很明确。

orig_dict = {'a' : 1, 'b' : 2}
new_dict = {'a' : 2, 'v' : 'hello', 'b' : 2}

updates = {}
for k2, v2 in new_dict.items():
    if k2 in orig_dict:    
        if v2 != orig_dict[k2]:
            updates.update({k2 : v2})
    else:
        updates.update({k2 : v2})

#test it
#value of 'a' was changed
#'v' is a completely new entry
assert all(k in updates for k in ['a', 'v'])

【讨论】:

    【解决方案7】:
    def flatten_it(d):
        if isinstance(d, list) or isinstance(d, tuple):
            return tuple([flatten_it(item) for item in d])
        elif isinstance(d, dict):
            return tuple([(flatten_it(k), flatten_it(v)) for k, v in sorted(d.items())])
        else:
            return d
    
    dict1 = {'a': 1, 'b': 2, 'c': 3}
    dict2 = {'a': 1, 'b': 1}
    
    print set(flatten_it(dict1)) - set(flatten_it(dict2)) # set([('b', 2), ('c', 3)])
    # or 
    print set(flatten_it(dict2)) - set(flatten_it(dict1)) # set([('b', 1)])
    

    【讨论】:

      【解决方案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) 元组。

      然后我们从中创建一个新的 dict 以将键折叠在一起并对其进行迭代。这些是唯一从一个字典更改为下一个字典的键。

      然后,我们使用这些键组成一个新的字典,并在键不存在时将每个字典中的值的元组替换为我们丢失的令牌。

      【讨论】:

      • 这很好用!但是当一个或两个 dicts 包含列表时它不起作用: set(a.items()) ^ set(b.items()) TypeError: unhashable type: 'list'
      【解决方案9】:

      老问题,但我想我还是会分享我的解决方案。很简单。

      dicta_set = set(dicta.items()) # creates a set of tuples (k/v pairs)
      dictb_set = set(dictb.items())
      setdiff = dictb_set.difference(dicta_set) # any set method you want for comparisons
      for k, v in setdiff: # unpack the tuples for processing
          print(f"k/v differences = {k}: {v}")
      

      此代码创建了两组表示 k/v 对的元组。然后它使用您选择的一组方法来比较元组。最后,它解包元组(k/v 对)进行处理。

      【讨论】:

        【解决方案10】:

        这将返回一个新的字典(仅更改数据)。

        def get_difference(obj_1: dict, obj_2: dict) -> dict:
        result = {}
        
        for key in obj_1.keys():
            value = obj_1[key]
        
            if isinstance(value, dict):
                difference = get_difference(value, obj_2.get(key, {}))
        
                if difference:
                    result[key] = difference
        
            elif value != obj_2.get(key):
                result[key] = obj_2.get(key, None)
        
        return result
        

        【讨论】:

          【解决方案11】:

          不确定这是 OP 要求的,但这是我遇到这个问题时正在寻找的 - 具体来说,如何按键显示两个字典之间的区别:

          陷阱:当一个 dict 缺少键,而第二个具有 None 值时,该函数将假定它们相似

          这根本没有优化 - 适用于小字典

          def diff_dicts(a, b, drop_similar=True):
              res = a.copy()
          
              for k in res:
                  if k not in b:
                      res[k] = (res[k], None)
          
              for k in b:
                  if k in res:
                      res[k] = (res[k], b[k])
                  else:
                      res[k] = (None, b[k])
          
              if drop_similar:
                  res = {k:v for k,v in res.items() if v[0] != v[1]}
          
              return res
          
          
          print(diff_dicts({'a': 1}, {}))
          print(diff_dicts({'a': 1}, {'a': 2}))
          print(diff_dicts({'a': 2}, {'a': 2}))
          print(diff_dicts({'a': 2}, {'b': 2}))
          print(diff_dicts({'a': 2}, {'a': 2, 'b': 1}))
          

          输出:

          {'a': (1, None)}
          {'a': (1, 2)}
          {}
          {'a': (2, None), 'b': (None, 2)}
          {'b': (None, 1)}
          

          【讨论】:

            【解决方案12】:

            解决方案是使用unittest 模块:

            from unittest import TestCase
            TestCase().assertDictEqual(expected_dict, actual_dict)
            

            How can you test that two dictionaries are equal with pytest in python获得

            【讨论】:

            • 这很酷,但在 Python 2 中不起作用:ValueError: no such test method in &lt;class 'unittest.case.TestCase'&gt;: runTest
            • @Matt 如果您仍在使用 Python 2,我深表哀悼!
            【解决方案13】:

            这是我自己的版本,结合https://stackoverflow.com/a/67263119/919692https://stackoverflow.com/a/48544451/919692,现在我发现它和https://stackoverflow.com/a/47433207/919692非常相似:

            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,现在应该已修复。
            【解决方案14】:

            我建议使用已经由优秀开发人员编写的内容。喜欢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测试

            【讨论】:

              【解决方案15】:

              你可以使用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}}}
              

              它可以让您查看更改的内容(甚至类型)、添加的内容和删除的内容。它还允许您执行许多其他操作,例如忽略重复项和路径(由正则表达式定义)。

              【讨论】:

                猜你喜欢
                • 2016-08-16
                • 1970-01-01
                • 1970-01-01
                • 2014-10-22
                • 2016-01-29
                • 2013-07-28
                • 2016-08-15
                • 1970-01-01
                • 2020-12-24
                相关资源
                最近更新 更多