【问题标题】:How to compare nested dicts如何比较嵌套的字典
【发布时间】:2018-07-17 02:11:34
【问题描述】:
a_standard = {
    'section1': {
        'category1': 1,
        'category2': 2
    },
    'section2': {
        'category1': 1,
        'category2': 2
    }

}

a_new = {
    'section1': {
        'category1': 1,
        'category2': 2
    },
    'section2': {
        'category1': 1,
        'category2': 3
    }

}

我想找出a_standarda_new 之间的区别,即a_new[section2][category2] 的值差异为23

我应该将每个转换为一个集合,然后进行差异或循环并比较字典吗?

【问题讨论】:

标签: python dictionary search hash set


【解决方案1】:

有一个名为 deepdiff 的库有很多选项,但我觉得它有点不直观。

这是一个递归函数,我经常在单元测试期间使用它来计算差异。这有点超出了问题的要求,因为我也处理了嵌套列表的情况。我希望你会发现它有用。

函数定义

from copy import deepcopy


def deep_diff(x, y, parent_key=None, exclude_keys=[], epsilon_keys=[]):
    """
    Take the deep diff of JSON-like dictionaries

    No warranties when keys, or values are None

    """
    EPSILON = 0.5
    rho = 1 - EPSILON

    if x == y:
        return None

    if parent_key in epsilon_keys:
        xfl, yfl = float_or_None(x), float_or_None(y)
        if xfl and yfl and xfl * yfl >= 0 and rho * xfl <= yfl and rho * yfl <= xfl:
            return None

    if type(x) != type(y) or type(x) not in [list, dict]:
        return x, y

    if type(x) == dict:
        d = {}
        for k in x.keys() ^ y.keys():
            if k in exclude_keys:
                continue
            if k in x:
                d[k] = (deepcopy(x[k]), None)
            else:
                d[k] = (None, deepcopy(y[k]))

        for k in x.keys() & y.keys():
            if k in exclude_keys:
                continue

            next_d = deep_diff(x[k], y[k], parent_key=k, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys)
            if next_d is None:
                continue

            d[k] = next_d

        return d if d else None

    # assume a list:
    d = [None] * max(len(x), len(y))
    flipped = False
    if len(x) > len(y):
        flipped = True
        x, y = y, x

    for i, x_val in enumerate(x):
        d[i] = deep_diff(y[i], x_val, parent_key=i, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys) if flipped else deep_diff(x_val, y[i], parent_key=i, exclude_keys=exclude_keys, epsilon_keys=epsilon_keys)

    for i in range(len(x), len(y)):
        d[i] = (y[i], None) if flipped else (None, y[i])

    return None if all(map(lambda x: x is None, d)) else d

# We need this helper function as well:
def float_or_None(x):
    try:
        return float(x)
    except ValueError:
        return None

用法

>>> deep_diff(a_standard, a_new)

{'section2': {'category2': (2, 3)}}

我认为输出比其他答案更直观。

在单元测试中我会这样:

import json

diff = deep_diff(expected_out, out, exclude_keys=["flickery1", "flickery2"])
assert diff is None, json.dumps(diff, indent=2)

【讨论】:

  • 感谢这个 sn-p,这是我需要的格式!我确实有一个问题,虽然我不知道如何解决,但在比较列表时,如果元素是字典,它似乎会在列表末尾添加 None 。希望这是有道理的!再次感谢!
  • 其实我这么说,好像是在某些点添加了一个字典列表。
  • 该代码中存在一些错误。我现在正在复制一个更好的版本。
  • 我希望解决了你的问题,但如果没有,我会很感激一个例子。
  • 嗨西番雅!刚刚设法解决了我面临的问题的一个例子!在脚本的底部,您将看到结果(因此您不必运行它)。到目前为止,我很感谢您的帮助,但我就是无法遵循您的代码,递归函数不是我的事...gist.github.com/geekscrapy/…
【解决方案2】:

你可以使用递归:

a_standard = {
'section1': {
    'category1': 1,
    'category2': 2
},
'section2': {
    'category1': 1,
    'category2': 2
 }

}

a_new = {
'section1': {
    'category1': 1,
    'category2': 2
},
'section2': {
    'category1': 1,
    'category2': 3
 }

}
def differences(a, b, section=None):
    return [(c, d, g, section) if all(not isinstance(i, dict) for i in [d, g]) and d != g else None if all(not isinstance(i, dict) for i in [d, g]) and d == g else differences(d, g, c) for [c, d], [h, g] in zip(a.items(), b.items())]

n = filter(None, [i for b in differences(a_standard, a_new) for i in b])

输出:

[('category2', 2, 3, 'section2')]

这会产生对应于不相等值的键。

编辑:没有列表理解:

def differences(a, b, section = None):
  for [c, d], [h, g] in zip(a.items(), b.items()):
      if not isinstance(d, dict) and not isinstance(g, dict):
         if d != g:
            yield (c, d, g, section)
      else:
          for i in differences(d, g, c):
             for b in i:
               yield b
print(list(differences(a_standard, a_new)))

输出:

['category2', 2, 3, 'section2']

此解决方案使用生成器(因此使用了yield 语句),它即时存储生成的值,只记住它停止的位置。可以通过将返回的结果转换为列表来获取这些值。 yield 使累积值差异变得更容易,并且无需在函数或全局变量中保留额外的参数。

【讨论】:

  • 干得好!有用。你能提供一个非pythonic的解决方案吗(我更容易理解)
  • 你太棒了 Ajax1234
  • 有什么方法可以显示您的代码发现差异的部分吗?
【解决方案3】:

假设密钥相同,您可以这样做:

def find_diff(dict1, dict2):
    differences = []
    for key in dict1.keys(): 
        if type(dict1[key]) is dict:
            return find_diff(dict1[key], dict2[key])
        else:
            if not dict1[key] == dict2[key]:
                differences.append((key, dict1[key], dict2[key]))
    return differences

我现在正在手机上打字,如果语法有点混乱,非常抱歉。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-11-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-19
    • 2017-02-16
    • 2022-11-21
    相关资源
    最近更新 更多