【问题标题】:How to check if 2 dicts are equal except some fields?除了某些字段外,如何检查 2 个字典是否相等?
【发布时间】:2019-03-06 12:20:36
【问题描述】:

有两个字典:旧的和更新的。我想检查它们是否相等,除了 statuslatitudelongitude 键。

assert old_dict['status'] != updated_dict['status']
assert old_dict['latitude'] != updated_dict['latitude']
assert old_dict['longitude'] != updated_dict['longitude']

for field in ('status', 'latitude', 'longitude'):
    updated_dict.pop(field)
    old_dict.pop(field)

assert old_dict == updated_dict

更pythonic的方法是什么?

【问题讨论】:

  • 状态(和纬度/经度)必须与您的代码中暗示的不同吗?
  • 您是否想检查例外键是否也不同(例如状态),或者您最初的断言只是为了证明字典会被认为是不相等的?也就是说,给定两个相同的字典,您是否希望比较结果为假,因为例外键的值相等。
  • 您是只对更 Pythonic 的解决方案感兴趣,还是对效率/速度也感兴趣?
  • @norok2 两个,你能提供“效率/速度”的解决方案吗?
  • 我添加了一些计时。它们确实有点取决于我解决您自己问题中的歧义的方式,以及正在测试的实际数据。

标签: python python-3.x dictionary


【解决方案1】:

试试这个:

old_dict.keys() == updated_dict.keys()

如果old_dict kyes 是update_dict 键的子集,则为真。

【讨论】:

  • 这不是 OP 的意思。 old_dicts 实际上也有这三个键,但只是具有不同的值,因此在 OP 的代码中断言不等式。
  • @blhsing我已经更新了我的答案,谢谢你说。
【解决方案2】:

有点不正统的建议,但请听我说:

differing = {"status", "latitude", "longitude"}
assert all(
    (old_dict[key] != updated_dict[key]) == (key in differing)
    for key in old_dict
)

对于每个key,我们断言当且仅当键是不同键之一时值不同。

【讨论】:

  • 如果update_dict 的键不在old_dict 中怎么办?不清楚这是否与 OP 有关。
  • 我喜欢这种方法(假设键确实相同),但我宁愿使用类似(old_dict[key] == updated_dict[key]) ^ (key in differing)
【解决方案3】:

您可以通过字典理解过滤您的两个字典,然后检查是否相等:

def compare_dicts(d1, d2, exc_keys):
    dct1 = {k: v for k, v in d1.items() if k not in exc_keys}
    dct2 = {k: v for k, v in d2.items() if k not in exc_keys}
    return dct1 == dct2

assert compare_dicts(old_dict, updated_dict, {'status', 'latitude', 'longitude'})

【讨论】:

    【解决方案4】:

    您可以断言两个dicts的项目之间的对称差异是三个键:

    assert {k for k, _ in old_dict.items() ^ updated_dict.items()} == {'status', 'latitude', 'longitude'}
    

    【讨论】:

      【解决方案5】:

      几行,但应该很快,因为它不会改变要比较的字典对的结构。

      EXC = {"status", "latitude", "longitude"}
      saved = {}
      
      for key in EXC:
         saved[key], updated_dict[key] = updated_dict[key], old_dict[key]
      cmp = old_dict == updated_dict
      for key in EXC:
         old_dict[key], updated_dict[key] = updated_dict[key], saved[key]
      

      【讨论】:

        【解决方案6】:

        怎么样

        ignore = {'status', 'latitude', 'longitude'}
        equal = all([old_value == new[key]
                     for key, old_value in old.items()
                     if key not in ignore])
        

        这会遍历字典一次(我认为没有办法解决)。

        【讨论】:

        • 我发现这个测试不完整,因为它假设两个 dicts 具有相同的键。但如果他们不这样做,测试可能会产生 KeyError 或误报。
        【解决方案7】:

        我假设完整的测试要求 exceptionexcluded dict 键必须不同,并且这两个字典可能没有所有键都相同。

        有些测试用例可以写成这样:

        import string
        import random
        
        random.seed(0)
        
        keys = list(string.ascii_letters)
        excluded = 'r', 'm', 'e'
        
        # the original dict
        base_dict = {key: random.randint(1, 100) for key in keys}
        
        # some keys, different from excluded are different
        unequal_dict = {key: (val if key not in ('q') else random.randint(1, 100)) for key, val in base_dict.items()}
        
        # only the excluded keys are different
        equal_dict = {key: (val if key not in excluded else random.randint(1, 100)) for key, val in base_dict.items()}
        
        # only some of the excluded keys are different
        partial_dict = {key: (val if key not in excluded[1:] else random.randint(1, 100)) for key, val in base_dict.items()}
        
        # a copy of the base dict
        identical_dict = base_dict.copy()
        
        # one more key is added
        not_same_keys_dict = base_dict.copy()
        not_same_keys_dict['aa'] = 1
        

        现在old_dict 基本上是base_dict,而unequal_dictequal_dictpartial_dictidentical_dictnot_same_keys_dict 涵盖不同的极端情况。

        然后,我们定义一些辅助函数来同时测试不同的输入。

        def multi_test(func, many_args):
            return [func(*args) for args in many_args]
        
        many_args = (
            (base_dict, unequal_dict, updated),
            (base_dict, equal_dict, updated),
            (base_dict, partial_dict, updated),
            (base_dict, identical_dict, updated),
            (base_dict, not_same_keys_dict, updated))
        

        功能化的原始代码如下所示:

        import copy
        
        def dicts_equal_except_orig(dict1, dict2, excluded):
            dict1 = dict1.copy()
            dict2 = dict2.copy()
            result = True
            for key in excluded:
                result = result and (dict1[key] != dict2[key])
                dict1.pop(key)
                dict2.pop(key)
            result = result and (dict1 == dict2)
            return result
        
        print(multi_test(dicts_equal_except_orig, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(dicts_equal_except_orig, many_args)
        # 13.1 µs ± 183 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
        

        假设要比较的字典有一些不共同的键,这与生成的测试一样快。 所有其他方法都慢得多,尽管可能更干净,在某些情况下甚至可能更快,例如当要排除的键数很大时等。 此外,如果不需要not_same_key 用例,即字典始终具有相同的键,则基于all() 的解决方案将更快,因为它们将具有显式短路,并且它们可以通过以下方式转换变化:

        keys = dict1.keys() | dict2.keys()
        

        例如

        keys = dict1.keys()
        

        并删除其他健全性检查,例如 if key in dict1 and key in dict2


        为了完整起见,我报告了我测试的所有其他选项:

        我自己的显式测试解决方案

        def dicts_equal_except(dict1, dict2, excluded):
            keys = dict1.keys() | dict2.keys()
            return all(
                (dict1[key] != dict2[key] if key in excluded else dict1[key] == dict2[key])
                if key in dict1 and key in dict2 else False
                for key in keys)
        
        
        print(multi_test(dicts_equal_except, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(dicts_equal_except, many_args)
        # 28.3 µs ± 186 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        @blhsing 解决方案的功能化

        def check_dict_except(dict1, dict2, excluded):
            return {k for k, _ in dict1.items() ^ dict2.items()} == set(excluded)
        
        print(multi_test(check_dict_except, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(check_dict_except, many_args)
        # 30.8 µs ± 498 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        @L3viathan 解决方案的变体

        def dicts_equal_all(dict1, dict2, excluded):
            keys = dict1.keys() | dict2.keys()
            return all((dict1[key] == dict2[key]) ^ (key in excluded) for key in keys)
        
        print(multi_test(dicts_equal_all, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(dicts_equal_all, many_args)
        # 29.7 µs ± 316 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        def dicts_equal_all2(dict1, dict2, excluded):
            keys = dict1.keys() | dict2.keys()
            return all((dict1[key] != dict2[key]) == (key in excluded) for key in keys)
        
        print(multi_test(dicts_equal_all2, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(dicts_equal_all2, many_args)
        # 29.9 µs ± 435 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        改编自@jpp 答案:

        def compare_dicts(dict1, dict2, excluded):
            filter_dict1 = {key: val for key, val in dict1.items() if key not in excluded}
            filter_dict2 = {key: val for key, val in dict2.items() if key not in excluded}
            excluded_dict1 = {key: dict1[key] for key in excluded if key in dict1}
            excluded_dict2 = {key: dict2[key] for key in excluded if key in dict2}
            return filter_dict1 == filter_dict2 and all(dict1[key] != dict2[key] if key in dict1 and key in dict2 else False for key in excluded)
        
        print(multi_test(compare_dicts, many_args))
        # [False, True, False, False, False]
        
        %timeit multi_test(compare_dicts, many_args)
        # 57.5 µs ± 960 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2018-07-02
          • 1970-01-01
          • 2013-11-16
          • 1970-01-01
          • 2015-12-21
          • 2015-08-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多