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

2025-02-27 09:05:00
admin
原创
62
摘要:问题描述:我有两本字典,我需要找出两者之间的区别,这应该给我一个键和一个值。我搜索并找到了一些插件/包,如 datadiff 和 dictdiff-master,但是当我尝试在 Python 2.7 中导入它们时,它说没有定义这样的模块。我在这里使用了一套:first_dict = {} second_dic...

问题描述:

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

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

我在这里使用了一套:

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

我的输出是:

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

我只获取了键,但还需要获取值。


解决方案 1:

我认为最好使用集合的对称差异运算来做到这一点这是文档的链接。

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

解决方案 2:

尝试以下代码片段,使用字典推导:

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

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

解决方案 3:

另一个解决方案是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)

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

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

解决方案 4:

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

它可以让您看到哪些内容发生了变化(甚至类型)、哪些内容被添加以及哪些内容被删除。它还可以让您执行许多其他操作,例如忽略重复项和忽略路径(由正则表达式定义)。

解决方案 5:

一种解决方案是使用unittest模块:

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

摘自如何使用 pytest 在 python 中测试两个字典是否相等

解决方案 6:

我建议使用优秀开发人员编写的程序。比如pytest。它可以处理任何数据类型,而不仅仅是字典。顺便说一句,pytest它非常擅长测试。

from _pytest.assertion.util import _compare_eq_any

print('
'.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'}
?        ^

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

PS: 已测试pytest==6.2.4

解决方案 7:

您考虑使用集合的做法是正确的,我们只需要深入挖掘一下就能让您的方法发挥作用。

一、示例代码:

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

解决方案 8:

这是我自己的版本,将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

解决方案 9:

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

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)从两个字典中生成一组不同的元组。

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

然后,我们使用这些键组成一个新的字典,当键不存在时,用每个字典中的值的元组替换我们丢失的标记。

解决方案 10:

此函数仅根据字典键为您提供所有差异(以及保持不变的内容)。它还突出显示了一些不错的 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())}

解决方案 11:

不确定这是否是 OP 所要求的,但当我遇到这个问题时,我正在寻找这个 - 具体来说,如何逐个键显示两个词典之间的差异:

陷阱:当一个字典缺少一个键,而第二个字典缺少一个 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:

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

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

解决方案 13:

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)])

解决方案 14:

老问题了,但无论如何我还是想分享我的解决方案。非常简单。

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 对的元组。然后,它使用您选择的 set 方法来比较元组。最后,它将元组(k/v 对)解包以进行处理。

解决方案 15:

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

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

解决方案 16:

对于单侧比较,你可以使用字典理解:

dict1 = {'a': 1, 'b': 2, 'c': 3, 'd': 4}
dict2 = {'a': OMG, 'b': 2, 'c': 3, 'd': 4}

data = {a:dict1[a] for a in dict1 if dict1[a] != dict2[a]}

输出:{'a': 1}

解决方案 17:

该解决方案与不可散列的字典完美兼容,可以修复此错误:

TypeError: Unhashable type 'dict'.

从@Roedy 的顶级解决方案开始。我们创建一个列表字典,这是不可哈希的一个很好的例子:

>>> dict1 = {1:['donkey'], 2:['chicken'], 3:['dog']}
>>> dict2 = {1:['donkey'], 2:['chimpansee'], 4:['chicken']}

然后我们使用以下方法进行预处理以使每个值可哈希化str(value)

>>> set1 = set([(key, str(value)) for key, value in dict1.items()])
>>> set2 = set([(key, str(value)) for key, value in dict2.items()])

然后我们按照@Reody 的回答继续:

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

解决方案 18:

如果您知道 dict2 中的值是正确的,则有一个变体可让您更新 dict1 的值。

考虑:

dict1.update((k, dict2.get(k)) for k, v in dict1.items())

解决方案 19:

a_dic={'a':1, 'b':2}
b_dic={'a':1, 'b':20}

sharedmLst = set(a_dic.items()).intersection(b_dic.items())
diff_from_b = set(a_dic.items()) - sharedmLst
diff_from_a = set(b_dic.items()) - sharedmLst

print("Among the items in a_dic, the item different from b_dic",diff_from_b)
print("Among the items in b_dic, the item different from a_dic",diff_from_a)

Result :
Among the items in a_dic, the item different from b_dic {('b', 2)}
Among the items in b_dic, the item different from a_dic {('b', 20)}

解决方案 20:

为了进行测试,datatest 包将检查字典、numpy 数组、pandas 数据框等中的差异。Datatest 还允许您设置浮点比较的容差。

from datatest import validate, accepted
def test_compare_dict():
    expected = {"key1": 0.5}
    actual = {"key1": 0.499}
    with accepted.tolerance(0.1):
        validate(expected, actual)

差异导致datatest.ValidationError包含相关的无效、偏差、缺失或额外项目。

解决方案 21:

在 Python 3 上我遇到了unhashable type: 'dict'错误。我知道 OP 要求使用 Python 2.7,但由于它已被弃用,因此这里有兼容 Python 3 的函数:

def dict_diff(a, b):
    diff = {}
    for k,v in a.items():
        if k not in b:
            diff[k] = v
        elif v != b[k]:
            diff[k] = '%s != %s' % (v, b[k])
    for k,v in b.items():
        if k not in a:
            diff[k] = v

    return diff

输出如下:

  d1 = {1:'donkey', 2:'chicken', 3:'dog'}
  d2 = {1:'donkey', 2:'chimpansee', 4:'chicken'}

  diff = dict_diff(d1, d2)
  # {2: 'chicken != chimpansee', 3: 'dog', 4: 'chicken'}

解决方案 22:

根据文档,Python 现在直接在键上提供集合操作。

键视图与集合类似,因为其条目是唯一且可哈希的。项目视图也具有与集合类似的操作,因为(键,值)对是唯一的,并且键是可哈希的。如果项目视图中的所有值也是可哈希的,则项目视图可以与其他集合互操作。(值视图不被视为与集合类似,因为条目通常不是唯一的。)对于与集合类似的视图,抽象基类 collections.abc.Set 定义的所有操作都可用(例如 ==、< 或 ^)。使用集合运算符时,与仅接受集合作为输入的集合不同,与集合类似视图接受任何可迭代对象作为另一个操作数。

因此,这有效并打印 {'a'}

dict1 = {'a': 1, 'b': 2, 'c': 3}
dict2 = {'b': 4, 'c': 5, 'd': 6}

# Get the keys of both dictionaries as sets
keys1 = dict1.keys()
keys2 = dict2.keys()

# Perform set subtraction
unique_keys = keys1 - keys2

print(unique_keys)

解决方案 23:

必须深入到任何深度。必须显示“关键线索”,准确显示何时dict报告了差异。可能需要递归。必须处理任何深度的任何类型的值。无需外部包。

def diff_dicts(expected_dict, found_dict, depth=0, key_trail=[]):
    found_keys = list(found_dict.keys())
    for key, expected_value in expected_dict.items():
        key_trail.append(key)
        if key in found_dict:
            found_keys.remove(key)
            found_value = found_dict[key]
            if found_value != expected_value:
                if isinstance(expected_value, dict) and isinstance(found_value, dict):
                    diff_dicts(expected_value, found_value, depth=depth+1, key_trail=copy.deepcopy(key_trail))
                else:
                    print(f"""*** difference for key {key} depth {depth}:
expected of type {type(expected_value)}: {expected_value}
found of type {type(found_value)}: {found_value}
key trail: {key_trail}""")
        else:
            print(f'*** no key "{key}" in found_dict depth {depth}:
{json.dumps(found_dict, indent=2)}
key trail: {key_trail}')
        key_trail.pop()
    for found_key in found_keys:
        print(f'*** key "{found_key}" in found_dict, but absent from expected_dict depth {depth}:
{json.dumps(expected_dict, indent=2)}
key trail: {key_trail}')


diff_dicts(expected_data, used_data)

注意:Python dicts 也可以包含lists 作为值。处理这个问题的代码 ( ... elif isinstance(expected_value, list) and isinstance(found_value, list):...) 添加了很多行来正确处理事情(必须进行测试,确保列表的长度相同,两个列表的每个元素都是一个dictdiff_dicts对于每对元素都通过,等等),但很明显。

解决方案 24:

这个解决方案对我来说效果很好。它将返回一个新字典,其中包含任何具有不同值或另一个字典中完全缺失的键/对。

diff = {x: dict_one[x] for x in dict_one if x not in dict_two or x in dict_two and dict_one[x] != dict_two[x]}

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3817  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2697  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   0  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   0  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   0  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用