【问题标题】:Comparing Python dictionaries and nested dictionaries比较 Python 字典和嵌套字典
【发布时间】:2014-12-03 07:26:37
【问题描述】:

我知道有几个类似的问题,但我的问题对我来说完全不同且困难。 我有两个字典:

d1 = {'a': {'b': {'cs': 10}, 'd': {'cs': 20}}}
d2 = {'a': {'b': {'cs': 30}, 'd': {'cs': 20}}, 'newa': {'q': {'cs': 50}}}

d1 有键 'a'd2 有键 'a''newa'(换句话说,d1 是我的旧字典,d2 是我的新字典)。

我想遍历这些字典,如果键相同,则检查其值(嵌套字典),例如当我在d2 中找到键'a' 时,我会检查是否有'b',如果有,则检查'cs' 的值(从10 更改为30),如果更改此值我想打印出来。

另一种情况是,我想从d2获取密钥'newa'作为新添加的密钥。

因此,在遍历这两个字典之后,这是预期的输出:

"d2" has new key "newa"
Value of "cs" is changed from 10 to 30 of key "b" which is of key "a"

我有以下代码,我正在尝试使用许多不起作用的循环,但也不是一个好的选择,因此我正在寻找是否可以使用递归代码获得预期的输出。

for k, v in d1.iteritems():
    for k1, v1 in d2.iteritems():
        if k is k1:
            print k
            for k2 in v:
                for k3 in v1:
                    if k2 is k3:
                        print k2, "sub key matched"

        else:
            print "sorry no match found"

【问题讨论】:

    标签: python dictionary comparison


    【解决方案1】:

    使用递归比较两个字典:

    为 python 3 编辑(也适用于 python 2):

    d1= {'a':{'b':{'cs':10},'d':{'cs':20}}}
    d2= {'a':{'b':{'cs':30} ,'d':{'cs':20}},'newa':{'q':{'cs':50}}}
    
    def findDiff(d1, d2, path=""):
        for k in d1:
            if k in d2:
                if type(d1[k]) is dict:
                    findDiff(d1[k],d2[k], "%s -> %s" % (path, k) if path else k)
                if d1[k] != d2[k]:
                    result = [ "%s: " % path, " - %s : %s" % (k, d1[k]) , " + %s : %s" % (k, d2[k])]
                    print("\n".join(result))
            else:
                print ("%s%s as key not in d2\n" % ("%s: " % path if path else "", k))
    
    print("comparing d1 to d2:")
    findDiff(d1,d2)
    print("comparing d2 to d1:")
    findDiff(d2,d1)
    

    Python 2 旧答案:

    def findDiff(d1, d2, path=""):
        for k in d1:
            if (k not in d2):
                print (path, ":")
                print (k + " as key not in d2", "\n")
            else:
                if type(d1[k]) is dict:
                    if path == "":
                        path = k
                    else:
                        path = path + "->" + k
                    findDiff(d1[k],d2[k], path)
                else:
                    if d1[k] != d2[k]:
                        print (path, ":")
                        print (" - ", k," : ", d1[k])
                        print (" + ", k," : ", d2[k])
    

    输出:

    comparing d1 to d2:
    a -> b: 
     - cs : 10
     + cs : 30
    comparing d2 to d1:
    a -> b: 
     - cs : 30
     + cs : 10
    

    【讨论】:

    • 有一个错误,因为path 在 for 循环中被重用。如果您重命名为 if path == "": nested_path = k,(以及在 else 和 findDiff 中),则 path 不会被修改
    【解决方案2】:

    修改了 user3 的代码,让它变得更好

    d1= {'as': 1, 'a':
            {'b':
                {'cs':10,
                 'qqq': {'qwe':1}
                },
                'd': {'csd':30}
            }
        }
    d2= {'as': 3, 'a':
            {'b':
                {'cs':30,
                 'qqq': 123
                },
                'd':{'csd':20}
            },
            'newa':
            {'q':
                {'cs':50}
            }
        }
    
    def compare_dictionaries(dict_1, dict_2, dict_1_name, dict_2_name, path=""):
        """Compare two dictionaries recursively to find non mathcing elements
    
        Args:
            dict_1: dictionary 1
            dict_2: dictionary 2
    
        Returns:
    
        """
        err = ''
        key_err = ''
        value_err = ''
        old_path = path
        for k in dict_1.keys():
            path = old_path + "[%s]" % k
            if not dict_2.has_key(k):
                key_err += "Key %s%s not in %s\n" % (dict_2_name, path, dict_2_name)
            else:
                if isinstance(dict_1[k], dict) and isinstance(dict_2[k], dict):
                    err += compare_dictionaries(dict_1[k],dict_2[k],'d1','d2', path)
                else:
                    if dict_1[k] != dict_2[k]:
                        value_err += "Value of %s%s (%s) not same as %s%s (%s)\n"\
                            % (dict_1_name, path, dict_1[k], dict_2_name, path, dict_2[k])
    
        for k in dict_2.keys():
            path = old_path + "[%s]" % k
            if not dict_1.has_key(k):
                key_err += "Key %s%s not in %s\n" % (dict_2_name, path, dict_1_name)
    
        return key_err + value_err + err
    
    
    a = compare_dictionaries(d1,d2,'d1','d2')
    print a
    

    输出:

    Key d2[newa] not in d1
    Value of d1[as] (1) not same as d2[as] (3)
    Value of d1[a][b][cs] (10) not same as d2[a][b][cs] (30)
    Value of d1[a][b][qqq] ({'qwe': 1}) not same as d2[a][b][qqq] (123)
    Value of d1[a][d][csd] (30) not same as d2[a][d][csd] (20)
    

    【讨论】:

    • 对于 Python3 版本,将 dict_2.has_key(k)dict_1.has_key(k) 分别替换为 k in dict_2k in dict_1
    • 有一个错误,key_err += "Key %s%s not in %s\n" % (dict_2_name, path, dict_2_name) 应该是key_err += "Key %s%s not in %s\n" % (dict_1_name, path, dict_2_name)
    【解决方案3】:

    为什么不使用 deepdiff 库。

    查看:https://github.com/seperman/deepdiff

    >>> from deepdiff import DeepDiff
    >>> t1 = {1:1, 3:3, 4:4}
    >>> t2 = {1:1, 3:3, 5:5, 6:6}
    >>> ddiff = DeepDiff(t1, t2)
    >>> print(ddiff)
    {'dictionary_item_added': {'root[5]', 'root[6]'}, 'dictionary_item_removed': {'root[4]'}}
    

    当然它更强大,查看文档了解更多。

    【讨论】:

      【解决方案4】:

      这应该提供您需要的有用功能:

      对于 Python 2.7

      def isDict(obj):
          return obj.__class__.__name__ == 'dict'
      
      def containsKeyRec(vKey, vDict):
          for curKey in vDict:
              if curKey == vKey or (isDict(vDict[curKey]) and containsKeyRec(vKey, vDict[curKey])):
                  return True
          return False
      
      def getValueRec(vKey, vDict):
          for curKey in vDict:
              if curKey == vKey:
                  return vDict[curKey]
              elif isDict(vDict[curKey]) and getValueRec(vKey, vDict[curKey]):
                  return containsKeyRec(vKey, vDict[curKey])
          return None
      
      d1= {'a':{'b':{'cs':10},'d':{'cs':20}}}
      d2= {'a':{'b':{'cs':30} ,'d':{'cs':20}},'newa':{'q':{'cs':50}}}
      
      for key in d1:
          if containsKeyRec(key, d2):
              print "dict d2 contains key: " + key
              d2Value = getValueRec(key, d2)
              if d1[key] == d2Value:
                  print "values are equal, d1: " + str(d1[key]) + ", d2: " + str(d2Value)
              else:
                  print "values are not equal, d1: " + str(d1[key]) + ", d2: " + str(d2Value)
      
          else:
              print "dict d2 does not contain key: " + key
      

      对于 Python 3(或更高版本):

      def id_dict(obj):
          return obj.__class__.__name__ == 'dict'
      
      
      def contains_key_rec(v_key, v_dict):
          for curKey in v_dict:
              if curKey == v_key or (id_dict(v_dict[curKey]) and contains_key_rec(v_key, v_dict[curKey])):
                  return True
          return False
      
      
      def get_value_rec(v_key, v_dict):
          for curKey in v_dict:
              if curKey == v_key:
                  return v_dict[curKey]
              elif id_dict(v_dict[curKey]) and get_value_rec(v_key, v_dict[curKey]):
                  return contains_key_rec(v_key, v_dict[curKey])
          return None
      
      
      d1 = {'a': {'b': {'cs': 10}, 'd': {'cs': 20}}}
      d2 = {'a': {'b': {'cs': 30}, 'd': {'cs': 20}}, 'newa': {'q': {'cs': 50}}}
      
      for key in d1:
      if contains_key_rec(key, d2):
          d2_value = get_value_rec(key, d2)
          if d1[key] == d2_value:
              print("values are equal, d1: " + str(d1[key]) + ", d2: " + str(d2_value))
              pass
          else:
              print("values are not equal:\n"
                    "list1: " + str(d1[key]) + "\n" +
                    "list2: " + str(d2_value))
      
      else:
          print("dict d2 does not contain key: " + key)
      

      【讨论】:

        【解决方案5】:

        对于 python 3 或更高版本, 用于比较任何数据的代码。

        def do_compare(data1, data2, data1_name, data2_name, path=""):
            if operator.eq(data1, data2) and not path:
                log.info("Both data have same content")
            else:
                if isinstance(data1, dict) and isinstance(data2, dict):
                    compare_dict(data1, data2, data1_name, data2_name, path)
                elif isinstance(data1, list) and isinstance(data2, list):
                    compare_list(data1, data2, data1_name, data2_name, path)
                else:
                    if data1 != data2:
                        value_err = "Value of %s%s (%s) not same as %s%s (%s)\n"\
                                    % (data1_name, path, data1, data2_name, path, data2)
                        print (value_err)
                # findDiff(data1, data2)
        
        def compare_dict(data1, data2, data1_name, data2_name, path):
            old_path = path
            for k in data1.keys():
                path = old_path + "[%s]" % k
                if k not in data2:
                    key_err = "Key %s%s not in %s\n" % (data1_name, path, data2_name)
                    print (key_err)
                else:
                    do_compare(data1[k], data2[k], data1_name, data2_name, path)
            for k in data2.keys():
                path = old_path + "[%s]" % k
                if k not in data1:
                    key_err = "Key %s%s not in %s\n" % (data2_name, path, data1_name)
                    print (key_err)
        
        def compare_list(data1, data2, data1_name, data2_name, path):
            data1_length = len(data1)
            data2_length = len(data2)
            old_path = path
            if data1_length != data2_length:
                value_err = "No: of items in %s%s (%s) not same as %s%s (%s)\n"\
                                    % (data1_name, path, data1_length, data2_name, path, data2_length)
                print (value_err)
            for index, item in enumerate(data1):
                path = old_path + "[%s]" % index
                try:
                    do_compare(data1[index], data2[index], data1_name, data2_name, path)
                except IndexError:
                    pass
        

        【讨论】:

          【解决方案6】:

          添加添加更多功能的版本:

          • 可以比较任意嵌套的类似 JSON 的字典和列表
          • 允许您指定要忽略的键(例如在易碎的单元测试中)
          • 让您可以指定具有数值的键,只要它们在一定百分比范围内,就会被视为相等

          如果您如下定义deep_diff 函数并在@rkatkam 的示例中调用它,您将得到:

          >>> deep_diff(d1, d2)
          
          {'newa': (None, {'q': {'cs': 50}}), 'a': {'b': {'cs': (10, 30)}}}
          

          这是函数定义:

          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
          
              """
              # pylint: disable=unidiomatic-typecheck
          
              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 not (isinstance(x, (list, dict)) and (isinstance(x, type(y)) or isinstance(y, type(x)))):
                  return x, y
          
              if isinstance(x, dict):
                  d = type(x)()  # handles OrderedDict's as well
                  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
          

          【讨论】:

            【解决方案7】:

            添加非递归解决方案。

              # Non Recursively traverses through a large nested dictionary
              # Uses a queue of dicts_to_process to keep track of what needs to be traversed rather than using recursion.
              # Slightly more complex than the recursive version, but arguably better as there is no risk of stack overflow from
              # too many levels of recursion
              def get_dict_diff_non_recursive(dict1, dict2):
                  dicts_to_process=[(dict1,dict2,"")]
                  while dicts_to_process:
                      d1,d2,current_path = dicts_to_process.pop()
                      for key in d1.keys():
                          current_path = os.path.join(current_path, f"{key}")
                          #print(f"searching path {current_path}")
                          if key not in d2 or d1[key] != d2[key]:
                              print(f"difference at {current_path}")
                          if type(d1[key]) == dict:
                              dicts_to_process.append((d1[key],d2[key],current_path))
                          elif type(d1[key]) == list and d1[key] and type(d1[key][0]) == dict:
                              for i in range(len(d1[key])):
                                  dicts_to_process.append((d1[key][i], d2[key][i],current_path))
            

            【讨论】:

              【解决方案8】:

              我不喜欢我在许多线程中找到的许多答案......他们中的很多人建议使用deepdiff,它非常强大,不要误会我的意思,但它只是没有给我我想要的输出不仅仅是一个差异字符串,或者是一个新构建的看起来很奇怪的字典,其中的新键是从原始的嵌套键中收集的......实际上返回的是一个带有原始键和增量值的真实字典。

              我的用例是发送较小的有效负载,如果在 MQTT 网络上没有差异,则不发送。

              我找到的解决方案是从这个link 中部分窃取的,但是对其进行了修改,只给了我增量。然后我递归解析它,如果它嵌套构建最终的差异字典,则再次调用diff_dict()。事实证明,它比那里的许多示例要简单得多。仅供参考,它不关心排序。

              我的解决方案:

              def diff_dict(d1, d2):
                  d1_keys = set(d1.keys())
                  d2_keys = set(d2.keys())
                  shared_keys = d1_keys.intersection(d2_keys)
                  shared_deltas = {o: (d1[o], d2[o]) for o in shared_keys if d1[o] != d2[o]}
                  added_keys = d2_keys - d1_keys
                  added_deltas = {o: (None, d2[o]) for o in added_keys}
                  deltas = {**shared_deltas, **added_deltas}
                  return parse_deltas(deltas)
              
              
              def parse_deltas(deltas: dict):
                  res = {}
                  for k, v in deltas.items():
                      if isinstance(v[0], dict):
                          tmp = diff_dict(v[0], v[1])
                          if tmp:
                              res[k] = tmp
                      else:
                          res[k] = v[1]
                  return res
              

              示例:

              original = {
                  'int': 1,
                  'float': 0.1000,
                  'string': 'some string',
                  'bool': True,
                  'nested1': {
                      'int': 2,
                      'float': 0.2000,
                      'string': 'some string2',
                      'bool': True,
                      'nested2': {
                          'string': 'some string3'
                      }
                  }
              }
              new = {
                  'int': 2,
                  'string': 'some string',
                  'nested1': {
                      'int': 2,
                      'float': 0.5000,
                      'string': 'new string',
                      'bool': False,
                      'nested2': {
                          'string': 'new string nested 2 time'
                      }
                  },
                  'test_added': 'added_val'
              }
              
              print(diff_dict(original, new))
              

              输出:

              {'int': 2, 'nested1': {'string': 'new string', 'nested2': {'string': 'new string nested 2 time'}, 'bool': False, 'float': 0.5}, 'test_added': 'added_val'}
              

              【讨论】:

                【解决方案9】:

                解决方案

                def compare_dicts(dict1, dict2, indent=4, level=0, offset=0):
                    if not (isinstance(dict1, dict) or isinstance(dict2, dict)):
                        if dict1 == dict2:
                            return 'OK!'
                        else:
                            return 'MISMATCH!'
                        
                    if level > 0:
                        print()
                    keys1 = set(dict1.keys())
                    keys2 = set(dict2.keys())
                    if len(keys1 | keys2) == 0:
                        return '' if level else None
                        
                    max_len = max(tuple(map(len, keys1 | keys2))) + 2
                    for key in keys1 & keys2:
                        print(' '*indent*level + f'{key+":":<{max_len}}', end='')
                        print(compare_dicts(dict1[key], dict2[key], indent=indent, level=level+1))
                    for key in keys1 - keys2:
                        print(' '*indent*level + f'{key+":":<{max_len}}'
                              + 'presented only in dict 1!', end='')
                    for key in keys2 - keys1:
                        print(' '*indent*level + f'{key+":":<{max_len}}'
                              + 'presented only in dict 2!', end='')
                        
                    return '' if level else None
                

                示例

                dict1 = {
                    'a': 1,
                    'b': {
                        'ba': 21,
                        'bb': 22,
                        'bc': 23,
                    },
                    'c': 3,
                    'd': 4,
                }
                
                dict2 = {
                    'a': 1,
                    'b': {
                        'ba': 21,
                        'bb': -22,
                    },
                    'c': 3,
                    'd': -4,
                    'e': 5,
                }
                
                compare_dicts(dict1, dict2)
                

                输出

                b: 
                    bb: MISMATCH!
                    ba: OK!
                    bc: presented only in dict 1!
                a: OK!
                d: MISMATCH!
                c: OK!
                e: presented only in dict 2!
                

                【讨论】:

                • 正如目前所写,您的答案尚不清楚。请edit 添加其他详细信息,以帮助其他人了解这如何解决所提出的问题。你可以找到更多关于如何写好答案的信息in the help center
                猜你喜欢
                • 1970-01-01
                • 2022-11-22
                • 2018-09-19
                • 2019-07-07
                • 2021-05-17
                • 1970-01-01
                • 2020-03-13
                • 1970-01-01
                • 2022-12-18
                相关资源
                最近更新 更多