【问题标题】:Flatten nested dictionaries, compressing keys扁平化嵌套字典,压缩键
【发布时间】:2011-08-27 00:29:14
【问题描述】:

假设你有一本像这样的字典:

{'a': 1,
 'c': {'a': 2,
       'b': {'x': 5,
             'y' : 10}},
 'd': [1, 2, 3]}

您将如何将其扁平化为:

{'a': 1,
 'c_a': 2,
 'c_b_x': 5,
 'c_b_y': 10,
 'd': [1, 2, 3]}

【问题讨论】:

  • 另外,它有一个库:github.com/ianlini/flatten-dict
  • 我看到答案中建议的方法的性能非常不同。
  • 问题的最后应该有:“让叶子路径上的所有级别的键都连接起来?”或将标题更改为“压缩(=连接)键”。对于搜索的人,问题中应该有“连接”。我正在寻找一种解决方案,该解决方案将给出叶子路径上的键的 list,而不是串联。你可以说使用split(),但是这个问题不鼓励其他直接方式。

标签: python dictionary


【解决方案1】:

如果你是 pythonic oneliners 的粉丝:

my_dict={'a': 1,'c': {'a': 2,'b': {'x': 5,'y' : 10}},'d': [1, 2, 3]}

list(pd.json_normalize(my_dict).T.to_dict().values())[0]

返回:

{'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd': [1, 2, 3]}

如果您有一个字典列表而不仅仅是一个字典,则可以从末尾保留[0]

【讨论】:

    【解决方案2】:

    使用生成器:

    def flat_dic_helper(prepand,d):
        if len(prepand) > 0:
            prepand = prepand + "_"
        for k in d:
            i = d[k]
            if isinstance(i, dict):
                r = flat_dic_helper(prepand + k,i)
                for j in r:
                    yield j
            else:
                yield (prepand + k,i)
    
    def flat_dic(d):
        return dict(flat_dic_helper("",d))
    
    d = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
    print(flat_dic(d))
    
    
    >> {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
    

    【讨论】:

    • type(i).__name__=='dict' 可以替换为type(i) is dict 或者更好的isinstance(d, dict)(或Mapping/MutableMapping)。
    【解决方案3】:

    原发帖者需要考虑两个大的考虑因素:

    1. 是否存在键空间破坏问题?例如,{'a_b':{'c':1}, 'a':{'b_c':2}} 将导致 {'a_b_c':???}。以下解决方案通过返回可迭代的对来规避问题。
    2. 如果性能是一个问题,key-reducer 功能(我在此称为“join”)是否需要访问整个 key-path,或者它是否可以在每个节点上执行 O(1) 工作?树?如果你想能够说joinedKey = '_'.join(*keys),那将花费你 O(N^2) 的运行时间。但是,如果您愿意说nextKey = previousKey+'_'+thisKey,那么您将获得 O(N) 时间。下面的解决方案让您可以同时执行这两种操作(因为您可以仅连接所有键,然后对它们进行后处理)。

    (性能可能不是问题,但我会详细说明第二点,以防其他人关心:在实现这一点时,有许多危险的选择。如果你递归地执行此操作并产生并重新产生,或者 anything 等价于多次接触节点(这很容易意外地做到),你正在做潜在的 O(N^2) 工作而不是 O(N)。这是因为也许你正在计算一个键a 然后a_1 然后a_1_i...,然后计算a 然后a_1 然后a_1_ii...,但实际上你不应该再次计算a_1。即使您没有重新计算它,重新生成它(“逐级”方法)同样糟糕。一个很好的例子是考虑{1:{1:{1:{1:...(N times)...{1:SOME_LARGE_DICTIONARY_OF_SIZE_N}...}}}} 上的性能)

    下面是我写的一个函数flattenDict(d, join=..., lift=...),它可以适应多种用途,可以做你想做的事。遗憾的是,在不产生上述性能损失的情况下,制作这个函数的惰性版本是相当困难的(许多像 chain.from_iterable 这样的 python 内置函数实际上效率不高,我只是在对这段代码的三个不同版本进行广泛测试之后才意识到这一点,然后才确定)这个)。

    from collections import Mapping
    from itertools import chain
    from operator import add
    
    _FLAG_FIRST = object()
    
    def flattenDict(d, join=add, lift=lambda x:(x,)):
        results = []
        def visit(subdict, results, partialKey):
            for k,v in subdict.items():
                newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey,lift(k))
                if isinstance(v,Mapping):
                    visit(v, results, newKey)
                else:
                    results.append((newKey,v))
        visit(d, results, _FLAG_FIRST)
        return results
    

    为了更好地理解发生了什么,下图是为不熟悉reduce(左)的人准备的,也称为“向左折叠”。有时它是用初始值代替 k0 绘制的(不是列表的一部分,传递给函数)。这里,J 是我们的 join 函数。我们用lift(k)预处理每个kn

                   [k0,k1,...,kN].foldleft(J)
                               /    \
                             ...    kN
                             /
           J(k0,J(k1,J(k2,k3)))
                           /  \
                          /    \
               J(J(k0,k1),k2)   k3
                        /   \
                       /     \
                 J(k0,k1)    k2
                     /  \
                    /    \
                   k0     k1
    

    这实际上与functools.reduce 相同,但我们的函数对树的所有关键路径都执行此操作。

    >>> reduce(lambda a,b:(a,b), range(5))
    ((((0, 1), 2), 3), 4)
    

    演示(否则我会放在文档字符串中):

    >>> testData = {
            'a':1,
            'b':2,
            'c':{
                'aa':11,
                'bb':22,
                'cc':{
                    'aaa':111
                }
            }
        }
    from pprint import pprint as pp
    
    >>> pp(dict( flattenDict(testData) ))
    {('a',): 1,
     ('b',): 2,
     ('c', 'aa'): 11,
     ('c', 'bb'): 22,
     ('c', 'cc', 'aaa'): 111}
    
    >>> pp(dict( flattenDict(testData, join=lambda a,b:a+'_'+b, lift=lambda x:x) ))
    {'a': 1, 'b': 2, 'c_aa': 11, 'c_bb': 22, 'c_cc_aaa': 111}    
    
    >>> pp(dict( (v,k) for k,v in flattenDict(testData, lift=hash, join=lambda a,b:hash((a,b))) ))
    {1: 12416037344,
     2: 12544037731,
     11: 5470935132935744593,
     22: 4885734186131977315,
     111: 3461911260025554326}
    

    性能:

    from functools import reduce
    def makeEvilDict(n):
        return reduce(lambda acc,x:{x:acc}, [{i:0 for i in range(n)}]+range(n))
    
    import timeit
    def time(runnable):
        t0 = timeit.default_timer()
        _ = runnable()
        t1 = timeit.default_timer()
        print('took {:.2f} seconds'.format(t1-t0))
    
    >>> pp(makeEvilDict(8))
    {7: {6: {5: {4: {3: {2: {1: {0: {0: 0,
                                     1: 0,
                                     2: 0,
                                     3: 0,
                                     4: 0,
                                     5: 0,
                                     6: 0,
                                     7: 0}}}}}}}}}
    
    import sys
    sys.setrecursionlimit(1000000)
    
    forget = lambda a,b:''
    
    >>> time(lambda: dict(flattenDict(makeEvilDict(10000), join=forget)) )
    took 0.10 seconds
    >>> time(lambda: dict(flattenDict(makeEvilDict(100000), join=forget)) )
    [1]    12569 segmentation fault  python
    

    ……唉,别以为是我的错……


    [由于审核问题,不重要的历史记录]

    关于涉嫌重复Flatten a dictionary of dictionaries (2 levels deep) of lists

    这个问题的解决方案可以通过sorted( sum(flatten(...),[]) )来实现。反过来是不可能的:虽然flatten(...) 确实可以通过映射一个高阶累加器从所谓的重复中恢复,但无法恢复键。 (编辑:事实证明,所谓的重复所有者的问题是完全不同的,因为它只处理精确到 2 级深度的字典,尽管该页面上的一个答案给出了一个通用的解决方案。)

    【讨论】:

    • 我不确定这是否与问题有关。此解决方案不会展平字典列表的字典项,即 {'a': [{'aa': 1}, {'ab': 2}]}。可以轻松更改 flattenDict 函数以适应这种情况。
    • 如果您需要问题中的下划线,请使用join(partialKey + '_',lift(k)
    • 如果字典中有整数作为键,则需要将lift(k)改为str(lift(k))以避免in visit(subdict, results, partialKey) 9 def visit(subdict, results, partialKey): 10 for k,v in subdict.items(): ---> 11 newKey = lift(k) if partialKey==_FLAG_FIRST else join(partialKey + ',',lift(k)) 12 if isinstance(v,Mapping): 13 visit(v, results, newKey) TypeError: can only concatenate str (not "int") to str
    • @questionto42:无需更改;这就是lift 参数的目的。您可以只设置flattenDict(..., join=lambda a,b:a+'_'+b, lift=repr)(或str,但由于关键冲突1'1',这不是一个好主意),而不是将lift 作为标识函数并修改通用代码。
    • 现在这看起来像是高等科学:) 不过,我明白你对碰撞的看法。
    【解决方案4】:

    您可以使用递归来扁平化您的字典。

    import collections
    
    
    def flatten(
        nested_dict,
        seperator='.',
        name=None,
    ):
        flatten_dict = {}
    
        if not nested_dict:
            return flatten_dict
    
        if isinstance(
            nested_dict,
            collections.abc.MutableMapping,
        ):
            for key, value in nested_dict.items():
                if name is not None:
                    flatten_dict.update(
                        flatten(
                            nested_dict=value,
                            seperator=seperator,
                            name=f'{name}{seperator}{key}',
                        ),
                    )
                else:
                    flatten_dict.update(
                        flatten(
                            nested_dict=value,
                            seperator=seperator,
                            name=key,
                        ),
                    )
        else:
            flatten_dict[name] = nested_dict
    
        return flatten_dict
    
    
    if __name__ == '__main__':
        nested_dict = {
            1: 'a',
            2: {
                3: 'c',
                4: {
                    5: 'e',
                },
                6: [1, 2, 3, 4, 5, ],
            },
        }
    
        print(
            flatten(
                nested_dict=nested_dict,
            ),
        )
    

    输出:

    {
       "1":"a",
       "2.3":"c",
       "2.4.5":"e",
       "2.6":[1, 2, 3, 4, 5]
    }
    

    【讨论】:

      【解决方案5】:

      或者,如果您已经在使用 pandas,您可以使用 json_normalize() 这样做:

      import pandas as pd
      
      d = {'a': 1,
           'c': {'a': 2, 'b': {'x': 5, 'y' : 10}},
           'd': [1, 2, 3]}
      
      df = pd.json_normalize(d, sep='_')
      
      print(df.to_dict(orient='records')[0])
      

      输出:

      {'a': 1, 'c_a': 2, 'c_b_x': 5, 'c_b_y': 10, 'd': [1, 2, 3]}
      

      【讨论】:

      • 或者只是传递 sep 参数:)
      • 有点遗憾它不处理列表:)
      • 我认为最新的版本是df = pd.io.json.json_normalize(original, sep='_')
      • 已弃用,最新的是:df = pd.json_normalize(d, sep='_')
      • @MohammadYusuf 我无法仅使用 json_normalize 函数中的参数将键转换为字符串。它内置在 JSON 端。也许,他们将来会改变它。它仍然是一个紧凑的单线,适用于字符串键的标准情况。
      【解决方案6】:

      这是一个使用堆栈的解决方案。没有递归。

      def flatten_nested_dict(nested):
          stack = list(nested.items())
          ans = {}
          while stack:
              key, val = stack.pop()
              if isinstance(val, dict):
                  for sub_key, sub_val in val.items():
                      stack.append((f"{key}_{sub_key}", sub_val))
              else:
                  ans[key] = val
          return ans
      

      【讨论】:

        【解决方案7】:

        实际上我最近写了一个名为cherrypicker的包来处理这种确切的事情,因为我不得不经常这样做!

        我认为下面的代码会为您提供您所追求的:

        from cherrypicker import CherryPicker
        
        dct = {
            'a': 1,
            'c': {
                'a': 2,
                'b': {
                    'x': 5,
                    'y' : 10
                }
            },
            'd': [1, 2, 3]
        }
        
        picker = CherryPicker(dct)
        picker.flatten().get()
        

        您可以使用以下方式安装软件包:

        pip install cherrypicker
        

        ...https://cherrypicker.readthedocs.io 上还有更多文档和指导。

        其他方法可能更快,但这个包的优先级是使这些任务容易。如果您确实有大量对象要展平,您也可以告诉 CherryPicker 使用并行处理来加快处理速度。

        【讨论】:

        • 我喜欢另一种方法。
        【解决方案8】:

        不完全是 OP 所要求的,但很多人来到这里寻找方法来展平现实世界的嵌套 JSON 数据,这些数据可以具有嵌套的键值 json 对象和数组以及数组内的 json 对象等等。 JSON 不包含元组,因此我们不必担心这些。

        我发现comment by @roneoanswer posted by @Imran 的列表包含实现:

        https://github.com/ScriptSmith/socialreaper/blob/master/socialreaper/tools.py#L8

        import collections
        def flatten(dictionary, parent_key=False, separator='.'):
            """
            Turn a nested dictionary into a flattened dictionary
            :param dictionary: The dictionary to flatten
            :param parent_key: The string to prepend to dictionary's keys
            :param separator: The string used to separate flattened keys
            :return: A flattened dictionary
            """
        
            items = []
            for key, value in dictionary.items():
                new_key = str(parent_key) + separator + key if parent_key else key
                if isinstance(value, collections.MutableMapping):
                    items.extend(flatten(value, new_key, separator).items())
                elif isinstance(value, list):
                    for k, v in enumerate(value):
                        items.extend(flatten({str(k): v}, new_key).items())
                else:
                    items.append((new_key, value))
            return dict(items)
        

        测试一下:

        flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3] })
        
        >> {'a': 1, 'c.a': 2, 'c.b.x': 5, 'c.b.y': 10, 'd.0': 1, 'd.1': 2, 'd.2': 3}
        

        这完成了我需要完成的工作:我将任何复杂的 json 扔到这里,它会为我弄平它。

        所有致谢https://github.com/ScriptSmith

        【讨论】:

        • 这是迄今为止我最喜欢的答案,因为它处理嵌套的字典列表。
        • 谢谢。我认为这是最好的,因为它也适用于列表。
        【解决方案9】:

        我尝试了此页面上的一些解决方案 - 尽管不是全部 - 但我尝试的那些解决方案未能处理 dict 的嵌套列表。

        考虑这样的字典:

        d = {
                'owner': {
                    'name': {'first_name': 'Steven', 'last_name': 'Smith'},
                    'lottery_nums': [1, 2, 3, 'four', '11', None],
                    'address': {},
                    'tuple': (1, 2, 'three'),
                    'tuple_with_dict': (1, 2, 'three', {'is_valid': False}),
                    'set': {1, 2, 3, 4, 'five'},
                    'children': [
                        {'name': {'first_name': 'Jessica',
                                  'last_name': 'Smith', },
                         'children': []
                         },
                        {'name': {'first_name': 'George',
                                  'last_name': 'Smith'},
                         'children': []
                         }
                    ]
                }
            }
        

        这是我的临时解决方案:

        def flatten_dict(input_node: dict, key_: str = '', output_dict: dict = {}):
            if isinstance(input_node, dict):
                for key, val in input_node.items():
                    new_key = f"{key_}.{key}" if key_ else f"{key}"
                    flatten_dict(val, new_key, output_dict)
            elif isinstance(input_node, list):
                for idx, item in enumerate(input_node):
                    flatten_dict(item, f"{key_}.{idx}", output_dict)
            else:
                output_dict[key_] = input_node
            return output_dict
        

        产生:

        {
          owner.name.first_name: Steven,
          owner.name.last_name: Smith,
          owner.lottery_nums.0: 1,
          owner.lottery_nums.1: 2,
          owner.lottery_nums.2: 3,
          owner.lottery_nums.3: four,
          owner.lottery_nums.4: 11,
          owner.lottery_nums.5: None,
          owner.tuple: (1, 2, 'three'),
          owner.tuple_with_dict: (1, 2, 'three', {'is_valid': False}),
          owner.set: {1, 2, 3, 4, 'five'},
          owner.children.0.name.first_name: Jessica,
          owner.children.0.name.last_name: Smith,
          owner.children.1.name.first_name: George,
          owner.children.1.name.last_name: Smith,
        }
        

        临时解决方案并不完美。
        注意:

        • 它不会保留空字典,例如 address: {} k/v 对。

        • 它不会扁平化嵌套元组中的字典 - 尽管使用 python 元组的行为类似于列表这一事实很容易添加。

        【讨论】:

        【解决方案10】:

        如果您不介意递归函数,这里有一个解决方案。我还冒昧地包含了一个 exclusion 参数,以防您希望保留一个或多个值。

        代码:

        def flatten_dict(dictionary, exclude = [], delimiter ='_'):
            flat_dict = dict()
            for key, value in dictionary.items():
                if isinstance(value, dict) and key not in exclude:
                    flatten_value_dict = flatten_dict(value, exclude, delimiter)
                    for k, v in flatten_value_dict.items():
                        flat_dict[f"{key}{delimiter}{k}"] = v
                else:
                    flat_dict[key] = value
            return flat_dict
        

        用法:

        d = {'a':1, 'b':[1, 2], 'c':3, 'd':{'a':4, 'b':{'a':7, 'b':8}, 'c':6}, 'e':{'a':1,'b':2}}
        flat_d = flatten_dict(dictionary=d, exclude=['e'], delimiter='.')
        print(flat_d)
        

        输出:

        {'a': 1, 'b': [1, 2], 'c': 3, 'd.a': 4, 'd.b.a': 7, 'd.b.b': 8, 'd.c': 6, 'e': {'a': 1, 'b': 2}}
        

        【讨论】:

          【解决方案11】:

          利用递归,使其简单易读:

          def flatten_dict(dictionary, accumulator=None, parent_key=None, separator="."):
              if accumulator is None:
                  accumulator = {}
          
              for k, v in dictionary.items():
                  k = f"{parent_key}{separator}{k}" if parent_key else k
                  if isinstance(v, dict):
                      flatten_dict(dictionary=v, accumulator=accumulator, parent_key=k)
                      continue
          
                  accumulator[k] = v
          
              return accumulator
          

          调用很简单:

          new_dict = flatten_dict(dictionary)
          

          new_dict = flatten_dict(dictionary, separator="_")
          

          如果我们想更改默认分隔符。

          小故障:

          当函数第一次被调用时,它只通过我们想要展平的dictionary 被调用。 accumulator 参数在这里是为了支持递归,我们稍后会看到。因此,我们将accumulator 实例化为一个空字典,我们将在其中放置原始dictionary 中的所有嵌套值。

          if accumulator is None:
              accumulator = {}
          

          当我们遍历字典的值时,我们为每个值构造一个键。第一次调用时,parent_key 参数将是 None,而对于每个嵌套字典,它将包含指向它的键,因此我们预先添加该键。

          k = f"{parent_key}{separator}{k}" if parent_key else k
          

          如果值 vk 指向的是一个字典,则函数调用自身,传递嵌套字典 accumulator (通过引用传递,因此对其所做的所有更改都是在同一个实例上完成)和键k,以便我们可以构造连接的键。注意continue 语句。我们想跳过if块之外的下一行,这样嵌套字典就不会出现在k键下的accumulator中。

          if isinstance(v, dict):
              flatten_dict(dict=v, accumulator=accumulator, parent_key=k)
              continue
          

          那么,如果值v 不是字典,我们该怎么办?只需在accumulator 内保持不变即可。

          accumulator[k] = v
          

          完成后,我们只返回 accumulator,而原始的 dictionary 参数保持不变。

          注意

          这仅适用于以字符串为键的字典。它将与实现 __repr__ 方法的可散列对象一起使用,但会产生不需要的结果。

          【讨论】:

            【解决方案12】:

            我正在考虑使用 UserDict 的子类来自动平键。

            class FlatDict(UserDict):
                def __init__(self, *args, separator='.', **kwargs):
                    self.separator = separator
                    super().__init__(*args, **kwargs)
            
                def __setitem__(self, key, value):
                    if isinstance(value, dict):
                        for k1, v1 in FlatDict(value, separator=self.separator).items():
                            super().__setitem__(f"{key}{self.separator}{k1}", v1)
                    else:
                        super().__setitem__(key, value)
            

            ‌ 可以即时添加键或使用标准 dict 实例化的优点,毫不奇怪:

            >>> fd = FlatDict(
            ...    {
            ...        'person': {
            ...            'sexe': 'male', 
            ...            'name': {
            ...                'first': 'jacques',
            ...                'last': 'dupond'
            ...            }
            ...        }
            ...    }
            ... )
            >>> fd
            {'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond'}
            >>> fd['person'] = {'name': {'nickname': 'Bob'}}
            >>> fd
            {'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob'}
            >>> fd['person.name'] = {'civility': 'Dr'}
            >>> fd
            {'person.sexe': 'male', 'person.name.first': 'jacques', 'person.name.last': 'dupond', 'person.name.nickname': 'Bob', 'person.name.civility': 'Dr'}
            

            【讨论】:

            • 分配给 fd['person'] 但保持其现有值是相当令人惊讶的。这不是常规 dicts 的工作方式。
            【解决方案13】:
            def flatten_nested_dict(_dict, _str=''):
                '''
                recursive function to flatten a nested dictionary json
                '''
                ret_dict = {}
                for k, v in _dict.items():
                    if isinstance(v, dict):
                        ret_dict.update(flatten_nested_dict(v, _str = '_'.join([_str, k]).strip('_')))
                    elif isinstance(v, list):
                        for index, item in enumerate(v):
                            if isinstance(item, dict):
                                ret_dict.update(flatten_nested_dict(item,  _str= '_'.join([_str, k, str(index)]).strip('_')))
                            else:
                                ret_dict['_'.join([_str, k, str(index)]).strip('_')] = item
                    else:
                        ret_dict['_'.join([_str, k]).strip('_')] = v
                return ret_dict
            

            【讨论】:

            • 这适用于我们嵌套字典中的列表,但没有自定义分隔符选项
            【解决方案14】:

            Flatten nested dictionaries, compressing keys 的变体,带有 max_level 和自定义减速器。

              def flatten(d, max_level=None, reducer='tuple'):
                  if reducer == 'tuple':
                      reducer_seed = tuple()
                      reducer_func = lambda x, y: (*x, y)
                  else:
                      raise ValueError(f'Unknown reducer: {reducer}')
            
                  def impl(d, pref, level):
                    return reduce(
                        lambda new_d, kv:
                            (max_level is None or level < max_level)
                            and isinstance(kv[1], dict)
                            and {**new_d, **impl(kv[1], reducer_func(pref, kv[0]), level + 1)}
                            or {**new_d, reducer_func(pref, kv[0]): kv[1]},
                            d.items(),
                        {}
                    )
            
                  return impl(d, reducer_seed, 0)
            

            【讨论】:

              【解决方案15】:

              如果您使用的是pandas,那么在pandas.io.json._normalize1 中隐藏了一个名为nested_to_record 的函数,它正是这样做的。

              from pandas.io.json._normalize import nested_to_record    
              
              flat = nested_to_record(my_dict, sep='_')
              

              1 在 pandas 版本 0.24.x 和更早的版本中使用 pandas.io.json.normalize(没有 _

              【讨论】:

              • 对我有用的是from pandas.io.json._normalize import nested_to_record。注意normalize之前的下划线(_)。
              • @EyalLevin 不错!这在0.25.x 中发生了变化,我已经更新了答案。 :)
              • 如果您将整数作为字典中的键,这将不起作用。 --&gt; 103 v = new_d.pop(k) 104 new_d.update(nested_to_record(v, newkey, sep, level + 1, max_level)) 105 new_ds.append(new_d) KeyError: '6'
              【解决方案16】:
              def flatten(unflattened_dict, separator='_'):
                  flattened_dict = {}
              
                  for k, v in unflattened_dict.items():
                      if isinstance(v, dict):
                          sub_flattened_dict = flatten(v, separator)
                          for k2, v2 in sub_flattened_dict.items():
                              flattened_dict[k + separator + k2] = v2
                      else:
                          flattened_dict[k] = v
              
                  return flattened_dict
              

              【讨论】:

                【解决方案17】:

                这不仅限于字典,而是每个实现 .items() 的映射类型。进一步更快,因为它避免了 if 条件。尽管如此,功劳归于伊姆兰:

                def flatten(d, parent_key=''):
                    items = []
                    for k, v in d.items():
                        try:
                            items.extend(flatten(v, '%s%s_' % (parent_key, k)).items())
                        except AttributeError:
                            items.append(('%s%s' % (parent_key, k), v))
                    return dict(items)
                

                【讨论】:

                • 如果d 不是dict 而是一个没有实现items 的自定义映射类型,那么你的函数就会立即失败。因此,它不适用于每种映射类型,而仅适用于实现 items() 的映射类型。
                • @user6037143 你遇到过没有实现items 的映射类型吗?我很想看到一个。
                • @user6037143,不,根据定义,如果未实现项目,则它不是映射类型。
                • @DavoudTaghawi-Nejad,您能否修改它以处理一般键,例如不应在内部展平的元组。
                【解决方案18】:

                我总是更喜欢通过.items() 访问dict 对象,因此为了扁平化字典,我使用以下递归生成器flat_items(d)。如果你想再次拥有dict,只需像这样包装它:flat = dict(flat_items(d))

                def flat_items(d, key_separator='.'):
                    """
                    Flattens the dictionary containing other dictionaries like here: https://stackoverflow.com/questions/6027558/flatten-nested-python-dictionaries-compressing-keys
                
                    >>> example = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
                    >>> flat = dict(flat_items(example, key_separator='_'))
                    >>> assert flat['c_b_y'] == 10
                    """
                    for k, v in d.items():
                        if type(v) is dict:
                            for k1, v1 in flat_items(v, key_separator=key_separator):
                                yield key_separator.join((k, k1)), v1
                        else:
                            yield k, v
                

                【讨论】:

                • 这是最好的功能解决方案。谢谢
                【解决方案19】:

                如果你想扁平化嵌套字典并想要所有唯一键列表,那么这里是解决方案:

                def flat_dict_return_unique_key(data, unique_keys=set()):
                    if isinstance(data, dict):
                        [unique_keys.add(i) for i in data.keys()]
                        for each_v in data.values():
                            if isinstance(each_v, dict):
                                flat_dict_return_unique_key(each_v, unique_keys)
                    return list(set(unique_keys))
                

                【讨论】:

                  【解决方案20】:

                  在简单的嵌套列表式递归中使用 dict.popitem():

                  def flatten(d):
                      if d == {}:
                          return d
                      else:
                          k,v = d.popitem()
                          if (dict != type(v)):
                              return {k:v, **flatten(d)}
                          else:
                              flat_kv = flatten(v)
                              for k1 in list(flat_kv.keys()):
                                  flat_kv[k + '_' + k1] = flat_kv[k1]
                                  del flat_kv[k1]
                              return {**flat_kv, **flatten(d)}
                  

                  【讨论】:

                    【解决方案21】:

                    扁平化嵌套字典的简单函数。对于 Python 3,将 .iteritems() 替换为 .items()

                    def flatten_dict(init_dict):
                        res_dict = {}
                        if type(init_dict) is not dict:
                            return res_dict
                    
                        for k, v in init_dict.iteritems():
                            if type(v) == dict:
                                res_dict.update(flatten_dict(v))
                            else:
                                res_dict[k] = v
                    
                        return res_dict
                    

                    想法/要求是: 获取不保留父键的平面字典。

                    使用示例:

                    dd = {'a': 3, 
                          'b': {'c': 4, 'd': 5}, 
                          'e': {'f': 
                                     {'g': 1, 'h': 2}
                               }, 
                          'i': 9,
                         }
                    
                    flatten_dict(dd)
                    
                    >> {'a': 3, 'c': 4, 'd': 5, 'g': 1, 'h': 2, 'i': 9}
                    

                    保留父键也很简单。

                    【讨论】:

                      【解决方案22】:

                      我使用生成器的 Python 3.3 解决方案:

                      def flattenit(pyobj, keystring=''):
                         if type(pyobj) is dict:
                           if (type(pyobj) is dict):
                               keystring = keystring + "_" if keystring else keystring
                               for k in pyobj:
                                   yield from flattenit(pyobj[k], keystring + k)
                           elif (type(pyobj) is list):
                               for lelm in pyobj:
                                   yield from flatten(lelm, keystring)
                         else:
                            yield keystring, pyobj
                      
                      my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}
                      
                      #your flattened dictionary object
                      flattened={k:v for k,v in flattenit(my_obj)}
                      print(flattened)
                      
                      # result: {'c_b_y': 10, 'd': [1, 2, 3], 'c_a': 2, 'a': 1, 'c_b_x': 5}
                      

                      【讨论】:

                      • 你可以扩展以处理除 str 之外的任何有效键类型(包括元组)?代替字符串连接,将它们加入一个元组。
                      【解决方案23】:

                      Python3.5 中的功能和高性能解决方案怎么样?

                      from functools import reduce
                      
                      
                      def _reducer(items, key, val, pref):
                          if isinstance(val, dict):
                              return {**items, **flatten(val, pref + key)}
                          else:
                              return {**items, pref + key: val}
                      
                      def flatten(d, pref=''):
                          return(reduce(
                              lambda new_d, kv: _reducer(new_d, *kv, pref), 
                              d.items(), 
                              {}
                          ))
                      

                      这更高效:

                      def flatten(d, pref=''):
                          return(reduce(
                              lambda new_d, kv: \
                                  isinstance(kv[1], dict) and \
                                  {**new_d, **flatten(kv[1], pref + kv[0])} or \
                                  {**new_d, pref + kv[0]: kv[1]}, 
                              d.items(), 
                              {}
                          ))
                      

                      使用中:

                      my_obj = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y': 10}}, 'd': [1, 2, 3]}
                      
                      print(flatten(my_obj)) 
                      # {'d': [1, 2, 3], 'cby': 10, 'cbx': 5, 'ca': 2, 'a': 1}
                      

                      【讨论】:

                      • 一个可读且有效的解决方案怎么样? ;) 你在哪个版本上测试过这个?在 Python 3.4.3 中尝试此操作时出现“语法错误”。似乎“**all”的使用是不合法的。
                      • 我从 Python 3.5 开始工作。不知道它不适用于3.4。你是对的,这不是很可读。我更新了答案。希望它现在更具可读性。 :)
                      • 添加了缺失的 reduce 导入。仍然觉得代码难以理解,我认为这是一个很好的例子,为什么 Guido van Rossum 本人在 2005 年就已经不鼓励使用 lambda、reduce、filter 和 map:artima.com/weblogs/viewpost.jsp?thread=98196
                      • 我同意。 Python 并不是真正为函数式编程而设计的。我仍然认为reduce 非常好,以防您需要减少字典。我更新了答案。现在应该看起来更像蟒蛇了。
                      【解决方案24】:

                      这是一种优雅的就地替换算法。使用 Python 2.7 和 Python 3.5 测试。使用点字符作为分隔符。

                      def flatten_json(json):
                          if type(json) == dict:
                              for k, v in list(json.items()):
                                  if type(v) == dict:
                                      flatten_json(v)
                                      json.pop(k)
                                      for k2, v2 in v.items():
                                          json[k+"."+k2] = v2
                      

                      例子:

                      d = {'a': {'b': 'c'}}                   
                      flatten_json(d)
                      print(d)
                      unflatten_json(d)
                      print(d)
                      

                      输出:

                      {'a.b': 'c'}
                      {'a': {'b': 'c'}}
                      

                      我发布了此代码here 以及匹配的unflatten_json 函数。

                      【讨论】:

                        【解决方案25】:

                        基本上与展平嵌套列表的方式相同,您只需要做额外的工作来按键/值迭代字典、为新字典创建新键并在最后一步创建字典。

                        import collections
                        
                        def flatten(d, parent_key='', sep='_'):
                            items = []
                            for k, v in d.items():
                                new_key = parent_key + sep + k if parent_key else k
                                if isinstance(v, collections.MutableMapping):
                                    items.extend(flatten(v, new_key, sep=sep).items())
                                else:
                                    items.append((new_key, v))
                            return dict(items)
                        
                        >>> flatten({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
                        {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
                        

                        【讨论】:

                        • 如果将isinstance 替换为try..except 块,这将适用于任何映射,即使它不是从dict 派生的。
                        • 将其更改为测试 collections.MutableMapping 以使其更通用。但是对于 Python try..except 可能是最好的选择。
                        • 如果您想在扁平化版本中保留空字典,您可能需要将 if isinstance(v, collections.MutableMapping): 更改为 if v and isinstance(v, collections.MutableMapping):
                        • 请注意,new_key = parent_key + sep + k if parent_key else k 假定键始终是字符串,否则会引发TypeError: cannot concatenate 'str' and [other] objects。但是,您可以通过简单地将 k 强制转换为字符串 (str(k)) 或将键连接到元组而不是字符串(元组也可以是 dict 键)来解决此问题。
                        • 回答了我自己的查询:我添加了一个“elif”,然后就成功了...elif isinstance(v,list): for idx,val in enumerate(v): new_key = str(parent_key) + sep + str(k) + sep + str(idx) if parent_key else str(k) + sep + str(idx) items.extend(Controller.flatten(v[idx],new_key,sep=sep).items())
                        【解决方案26】:

                        Davoud 的解决方案非常好,但是当嵌套的 dict 还包含 dicts 列表时没有给出令人满意的结果,但他的代码适用于这种情况:

                        def flatten_dict(d):
                            items = []
                            for k, v in d.items():
                                try:
                                    if (type(v)==type([])): 
                                        for l in v: items.extend(flatten_dict(l).items())
                                    else: 
                                        items.extend(flatten_dict(v).items())
                                except AttributeError:
                                    items.append((k, v))
                            return dict(items)
                        

                        【讨论】:

                        • 您可以缓存type([]) 的结果,以避免对dict 的每个项目进行函数调用。
                        • 请改用isinstance(v, list)
                        【解决方案27】:

                        这是一种“功能性”、“单线”的实现。它是递归的,基于条件表达式和字典理解。

                        def flatten_dict(dd, separator='_', prefix=''):
                            return { prefix + separator + k if prefix else k : v
                                     for kk, vv in dd.items()
                                     for k, v in flatten_dict(vv, separator, kk).items()
                                     } if isinstance(dd, dict) else { prefix : dd }
                        

                        测试:

                        In [2]: flatten_dict({'abc':123, 'hgf':{'gh':432, 'yu':433}, 'gfd':902, 'xzxzxz':{"432":{'0b0b0b':231}, "43234":1321}}, '.')
                        Out[2]: 
                        {'abc': 123,
                         'gfd': 902,
                         'hgf.gh': 432,
                         'hgf.yu': 433,
                         'xzxzxz.432.0b0b0b': 231,
                         'xzxzxz.43234': 1321}
                        

                        【讨论】:

                        • 这不适用于一般字典,特别是对于元组键,例如用 ('hgf',2) 替换测试中的第二个键 TypeError
                        • @alancalvitti 这假定它是一个字符串,或者其他支持+ 运算符的东西。对于其他任何事情,您都需要调整 prefix + separator + k 以适应适当的函数调用来组合对象。
                        • 另一个与元组键相关的问题。我已经单独发布了如何根据您的方法进行概括。但是它无法正确处理 ninjageko 的示例:{'a_b':{'c':1}, 'a':{'b_c':2}}
                        • 我开始担心,使用递归看不到答案。这些天我们的年轻人怎么了?
                        • 如果字典有嵌套的字典列表,则不执行任何操作,如下所示:{'name': 'Steven', 'children': [{'name': 'Jessica', 'children': []}, {'name': 'George', 'children': []}]}
                        【解决方案28】:

                        上面的答案非常有效。只是想我会添加我写的 unflatten 函数:

                        def unflatten(d):
                            ud = {}
                            for k, v in d.items():
                                context = ud
                                for sub_key in k.split('_')[:-1]:
                                    if sub_key not in context:
                                        context[sub_key] = {}
                                    context = context[sub_key]
                                context[k.split('_')[-1]] = v
                            return ud
                        

                        注意:这不考虑键中已经存在的“_”,就像扁平对应物一样。

                        【讨论】:

                          【解决方案29】:

                          代码:

                          test = {'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]}
                          
                          def parse_dict(init, lkey=''):
                              ret = {}
                              for rkey,val in init.items():
                                  key = lkey+rkey
                                  if isinstance(val, dict):
                                      ret.update(parse_dict(val, key+'_'))
                                  else:
                                      ret[key] = val
                              return ret
                          
                          print(parse_dict(test,''))
                          

                          结果:

                          $ python test.py
                          {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
                          

                          我用的是python3.2,更新为你的python版本。

                          【讨论】:

                          • 您可能希望在函数定义中而不是在调用函数时指定lkey='' 的默认值。请参阅这方面的其他答案。
                          【解决方案30】:

                          这与 imran 和 ralu 的回答相似。它不使用生成器,而是使用带有闭包的递归:

                          def flatten_dict(d, separator='_'):
                            final = {}
                            def _flatten_dict(obj, parent_keys=[]):
                              for k, v in obj.iteritems():
                                if isinstance(v, dict):
                                  _flatten_dict(v, parent_keys + [k])
                                else:
                                  key = separator.join(parent_keys + [k])
                                  final[key] = v
                            _flatten_dict(d)
                            return final
                          
                          >>> print flatten_dict({'a': 1, 'c': {'a': 2, 'b': {'x': 5, 'y' : 10}}, 'd': [1, 2, 3]})
                          {'a': 1, 'c_a': 2, 'c_b_x': 5, 'd': [1, 2, 3], 'c_b_y': 10}
                          

                          【讨论】:

                          • 我不确定在这里使用术语“closure”是否正确,因为函数_flatten_dict 永远不会返回,也不会被返回。它也许可以被称为子函数封闭函数
                          猜你喜欢
                          • 1970-01-01
                          • 2018-11-09
                          • 2019-02-04
                          • 1970-01-01
                          • 2019-03-02
                          • 2017-03-19
                          • 1970-01-01
                          相关资源
                          最近更新 更多