比较两个字典并检查有多少个(key, value)对相等

354

我有两个字典,但为了简化问题,我将仅使用这两个:

>>> x = dict(a=1, b=2)
>>> y = dict(a=2, b=2)

现在,我想比较 x 中的每一个 key, value 对是否有与之对应的相同的值在 y 中。所以我写了这个:

>>> for x_values, y_values in zip(x.iteritems(), y.iteritems()):
        if x_values == y_values:
            print 'Ok', x_values, y_values
        else:
            print 'Not', x_values, y_values

这有效,因为它返回一个元组,然后进行相等比较。

我的问题:

这是正确的吗?有没有更好的方法?我指的是代码的优雅程度,而不是速度上的优化。

更新:我忘记提到我需要检查有多少个键、值对是相等的。


36
根据https://dev59.com/FXI-5IYBdhLWcg3wVWti#5635309,``x == y``应该为真。 - Natim
2
x == y 应该为真。可以在 REPL 中快速检查。请参阅:https://docs.python.org/2/library/stdtypes.html#mapping-types-dict - Vikrant
2
根据官方文档x == y应该为真:“字典只有在它们具有相同的(键,值)对(无论顺序如何)时才相等。顺序比较(‘<’,‘<=’,‘>=’,‘>’)会引发TypeError错误。” - MestreLion
29个回答

256
如果你想知道两个字典中有多少值是匹配的,那么你应该这样说:)
也许可以这样表达:
shared_items = {k: x[k] for k in x if k in y and x[k] == y[k]}
print(len(shared_items))

1
如果字典键有列表元素,则会出现相同的错误。我认为使用cmp是更好的方法,除非我漏掉了什么。 - Mutant
2
@Mutant,那是另一个问题。首先,您无法创建具有“list”键的字典。 x = {[1,2]: 2} 将失败。该问题已经有有效的“dicts”。 - AnnanFay
11
问题是“比较两个字典[...]”。上面的“无效字典”使用了列表作为键,这不是有效的Python代码 - 字典的“键”必须是不可变的。因此,您没有在比较字典。如果您尝试将列表用作字典键,则代码将无法运行。您没有对象可以进行比较。这就像输入x = dict(23\;dfg&^*$^%$^$%^),然后抱怨字典的比较不起作用一样。当然它不会工作。另一方面,Tim的评论涉及可变的“值”,因此我说这些是不同的问题。 - AnnanFay
@TimTisdall 如果你像这样测试两个键和两个值,那么你可以规避 TypeError: set(x.keys())set(x.values())。但如果字典嵌套层数大于1,你需要递归地执行相同操作。 - MikeyE
1
@MikeyE - set 要求值必须是可哈希的,而 dict 要求键必须是可哈希的。set(x.keys()) 总是有效的,因为键必须是可哈希的,但是对于不可哈希的值,set(x.values()) 将会失败。 - Tim Tisdall
显示剩余3条评论

227
def dict_compare(d1, d2):
    d1_keys = set(d1.keys())
    d2_keys = set(d2.keys())
    shared_keys = d1_keys.intersection(d2_keys)
    added = d1_keys - d2_keys
    removed = d2_keys - d1_keys
    modified = {o : (d1[o], d2[o]) for o in shared_keys if d1[o] != d2[o]}
    same = set(o for o in shared_keys if d1[o] == d2[o])
    return added, removed, modified, same

x = dict(a=1, b=2)
y = dict(a=2, b=2)
added, removed, modified, same = dict_compare(x, y)

12
这个实际上处理了字典中可变值的情况! - Tim Tisdall
1
当我运行这个程序时,仍然会出现一个错误,涉及到可变值: ValueError: DataFrame的真值是模棱两可的。请使用a.empty、a.bool()、a.item()、a.any()或a.all()。 - Afflatus
3
按设计,DataFrame 不允许真值比较(除非长度为 1),因为它们继承自 numpy.ndarray。 - Daniel Myers

214

你想做的是简单地使用x==y

你所做的并不是一个好主意,因为字典中的项目不应该有任何顺序。你可能会将[('a',1),('b',1)][('b',1), ('a',1)](同一字典,不同顺序)进行比较。

例如,请参见以下示例:

>>> x = dict(a=2, b=2,c=3, d=4)
>>> x
{'a': 2, 'c': 3, 'b': 2, 'd': 4}
>>> y = dict(b=2,c=3, d=4)
>>> y
{'c': 3, 'b': 2, 'd': 4}
>>> zip(x.iteritems(), y.iteritems())
[(('a', 2), ('c', 3)), (('c', 3), ('b', 2)), (('b', 2), ('d', 4))]

这两者之间的区别只有一项,但你的算法会认为所有的项都不同。


@THC4k,抱歉没有提到。但我必须检查两个字典中有多少个值匹配。 - user225312
1
@A A:我解释了为什么你的代码在计数时不起作用。 - Jochen Ritzel
15
从 Python 3.6 开始,dict 默认是有序的。 - Phil
1
@JochenRitzel:对于字典内部的深度嵌套字典,这个方法如何运作? 我尝试在深度嵌套的字典上使用它,值完全相同但顺序不同,比较失败了。 - JavaSa
关于以下代码:In [7]: y = {"key": 1} In [8]: x == y Out[8]: True您有什么看法? - luca.giovagnoli
显示剩余3条评论

207

dic1 == dic2

来自 Python文档

The following examples all return a dictionary equal to {"one": 1, "two": 2, "three": 3}:

>>> a = dict(one=1, two=2, three=3)
>>> b = {'one': 1, 'two': 2, 'three': 3}
>>> c = dict(zip(['one', 'two', 'three'], [1, 2, 3]))
>>> d = dict([('two', 2), ('one', 1), ('three', 3)])
>>> e = dict({'three': 3, 'one': 1, 'two': 2})
>>> a == b == c == d == e
True
在第一个示例中,仅使用关键字参数适用于有效的 Python 标识符作为键。否则,可以使用任何有效的键。
对于 `python2` 和 `python3` 有效。

3
我不同意@ErkinAlpGüney。你能提供证据吗? - Qi Luo
6
我不同意 @ErkinAlpGüney 的观点。官方文档显示 == 确实是通过值比较字典,而不是地址。 https://docs.python.org/2/library/stdtypes.html#mapping-types-dict - Matthew Nakayama
3
适用于Python 2.7.13。 - Jesuisme
7
有序字典不等于普通字典。 - Pedro Lobito
3
请提供一个反例,证明这不成立。 - Pedro Lobito
显示剩余9条评论

100

由于似乎没有人提到 deepdiff,我将其添加到这里以便完整性。我发现它非常方便,可用于获取(嵌套)对象的差异:

安装

pip install deepdiff

样例代码

import deepdiff
import json

dict_1 = {
    "a": 1,
    "nested": {
        "b": 1,
    }
}

dict_2 = {
    "a": 2,
    "nested": {
        "b": 2,
    }
}

diff = deepdiff.DeepDiff(dict_1, dict_2)
print(json.dumps(diff, indent=4))

输出

{
    "values_changed": {
        "root['a']": {
            "new_value": 2,
            "old_value": 1
        },
        "root['nested']['b']": {
            "new_value": 2,
            "old_value": 1
        }
    }
}
关于漂亮地打印结果以便查看的说明:上述代码适用于两个字典具有相同属性键(例如示例中一样,可能存在不同的属性值)的情况。但是,如果其中一个字典存在额外的"extra"属性,则json.dumps()会失败。
TypeError: Object of type PrettyOrderedSet is not JSON serializable

解决方案:使用diff.to_json()json.loads() / json.dumps()进行漂亮打印:

import deepdiff
import json

dict_1 = {
    "a": 1,
    "nested": {
        "b": 1,
    },
    "extra": 3
}

dict_2 = {
    "a": 2,
    "nested": {
        "b": 2,
    }
}

diff = deepdiff.DeepDiff(dict_1, dict_2)
print(json.dumps(json.loads(diff.to_json()), indent=4))  

输出:

{
    "dictionary_item_removed": [
        "root['extra']"
    ],
    "values_changed": {
        "root['a']": {
            "new_value": 2,
            "old_value": 1
        },
        "root['nested']['b']": {
            "new_value": 2,
            "old_value": 1
        }
    }
}

另一种选择:使用pprint,可以得到不同的格式:

import pprint

# same code as above

pprint.pprint(diff, indent=4)

输出:

{   'dictionary_item_removed': [root['extra']],
    'values_changed': {   "root['a']": {   'new_value': 2,
                                           'old_value': 1},
                          "root['nested']['b']": {   'new_value': 2,
                                                     'old_value': 1}}}

这应该放在顶部。完美地工作并且整洁! - Fusseldieb

65

我刚开始学习Python,但最终做了与@mouad类似的事情。

unmatched_item = set(dict_1.items()) ^ set(dict_2.items())
len(unmatched_item) # should be 0

XOR运算符(^)应该在两个字典中的元素相同时清除所有相同的元素。


34
抱歉,如果字典中的值是可变的(即不可哈希的),则此方法无法正常工作。例如 {'a':{'b':1}} 会产生 TypeError: unhashable type: 'dict' 错误提示。 - Tim Tisdall

49

只需使用:

assert cmp(dict1, dict2) == 0

7
任务似乎不仅是检查两者内容是否相同,还要报告它们之间的差异。 - Diego Tercero
31
我认为这与 dict1 == dict2 是相同的。 - Trey Hunner
11
对于使用 Python3.5 的用户,内置函数 cmp 已被删除(应视为早已删除)。他们提出的替代方案是:(a > b) - (a < b) == cmp(a, b),以获得等效的功能(或更好的 __eq____hash__)。 - nerdwaller
我们可以使用cmp(dict1,dict2)来比较值为无序列表的字典,例如:dict1={'a':[0,1]}和dict2={'b':[1,0]}吗? - msc87
3
字典(dict)类型不支持排序,因此使用 dict_a > dict_b 会导致 TypeError:unorderable types: dict() < dict()。请注意,字典的大小比较只适用于 Python 3.7 版本以上的字典,且仅限于相等性测试(即 ==!=)。 - Stefano
2
@Stefano:说得好,我的评论更多是针对Python的一般比较(我没有注意实际答案,我的错误)。 - nerdwaller

11

@mouad的答案很好,但是只适用于包含简单值的字典。然而,如果你有包含字典的字典,你会得到一个异常,因为字典不可哈希。

我能想到的一种方法是:

def compare_dictionaries(dict1, dict2):
     if dict1 is None or dict2 is None:
        print('Nones')
        return False

     if (not isinstance(dict1, dict)) or (not isinstance(dict2, dict)):
        print('Not dict')
        return False

     shared_keys = set(dict1.keys()) & set(dict2.keys())

     if not ( len(shared_keys) == len(dict1.keys()) and len(shared_keys) == len(dict2.keys())):
        print('Not all keys are shared')
        return False


     dicts_are_equal = True
     for key in dict1.keys():
         if isinstance(dict1[key], dict) or isinstance(dict2[key], dict):
             dicts_are_equal = dicts_are_equal and compare_dictionaries(dict1[key], dict2[key])
         else:
             dicts_are_equal = dicts_are_equal and all(atleast_1d(dict1[key] == dict2[key]))

     return dicts_are_equal

1
如果您使用not isinstance(dict1, dict)而不是type(dict1) is not dict,这将适用于基于dict的其他类。此外,您可以使用all(atleast_1d(dict1[key] == dict2[key]))而不是(dict1[key] == dict2[key])来处理至少包含一个数组。 - EL_DON
+1,但是一旦dicts_are_equal变为false,您可以立即退出for loop。没有必要继续进行下去。 - pfabri
我自己也很惊讶,但似乎我可以直接使用==(使用python3.8)比较嵌套字典。```>>> dict2 = {"a": {"a": {"a": "b"}}}
dict1 = {"a": {"a": {"a": "b"}}} dict1 == dict2 True dict1 = {"a": {"a": {"a": "a"}}} dict1 == dict2 False```
- thiezn

9

我认为这个函数很好,清晰易懂。但是为了给你提供(另一个)答案,这是我的意见:

def compare_dict(dict1, dict2):
    for x1 in dict1.keys():
        z = dict1.get(x1) == dict2.get(x1)
        if not z:
            print('key', x1)
            print('value A', dict1.get(x1), '\nvalue B', dict2.get(x1))
            print('-----\n')

这对你或其他人可能有用..

编辑:

我已经创建了一个递归版本,没有在其他答案中看到过。

def compare_dict(a, b):
    # Compared two dictionaries..
    # Posts things that are not equal..
    res_compare = []
    for k in set(list(a.keys()) + list(b.keys())):
        if isinstance(a[k], dict):
            z0 = compare_dict(a[k], b[k])
        else:
            z0 = a[k] == b[k]

        z0_bool = np.all(z0)
        res_compare.append(z0_bool)
        if not z0_bool:
            print(k, a[k], b[k])
    return np.all(res_compare)

3
让我们改进它,使其双向工作。第二行代码:"for x1 in set(dict1.keys()).union(dict2.keys()):" - nkadwa
谢谢@nkadwa,现在可以了。 - zwep

9
最简单且较为可靠的比较两个字典的方法是将其序列化为JSON格式,排序键,并比较字符串结果:
import json
if json.dumps(x, sort_keys=True) == json.dumps(y, sort_keys=True):
   ... Do something ...

1
最明确的,尽管不是最快的。 - wowkin2

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