【问题标题】:How to iterate through an N-level nested dictionary in Python?如何在 Python 中遍历 N 级嵌套字典?
【发布时间】:2021-09-30 01:45:05
【问题描述】:

我发现自己制作多级词典相当多。我总是必须编写非常冗长的代码来遍历具有大量临时变量的字典的所有级别。

有没有办法推广这个函数来迭代多个级别,而不是硬编码并手动指定有多少级别?

def iterate_multilevel_dictionary(d, number_of_levels):
    # How to auto-detect number of levels? 
    # number_of_levels = 0
    if number_of_levels == 1:
        for k1, v1 in d.items():
            yield k1, v1
    if number_of_levels == 2:
        for k1, v1 in d.items():
            for k2, v2 in v1.items():
                yield k1, k2, v2
    if number_of_levels == 3:
        for k1, v1 in d.items():
            for k2, v2 in v1.items():
                for k3, v3 in v2.items():
                    yield k1, k2, k3, v3
                    
# Level 1
d_level1 = {"a":1,"b":2,"c":3}
for items in iterate_multilevel_dictionary(d_level1, number_of_levels=1):
    print(items)
# ('a', 1)
# ('b', 2)
# ('c', 3)

# Level 2
d_level2 = {"group_1":{"a":1}, "group_2":{"b":2,"c":3}}
for items in iterate_multilevel_dictionary(d_level2, number_of_levels=2):
    print(items)
#('group_1', 'a', 1)
#('group_2', 'b', 2)
#('group_2', 'c', 3)

# Level 3
d_level3 = {"collection_1":d_level2}
for items in iterate_multilevel_dictionary(d_level3, number_of_levels=3):
    print(items)
# ('collection_1', 'group_1', 'a', 1)
# ('collection_1', 'group_2', 'b', 2)
# ('collection_1', 'group_2', 'c', 3)

【问题讨论】:

  • 这是递归的罕见的强用例之一,因为嵌套的深度很少会导致堆栈问题。您是否尝试过递归方法?
  • 不是临时的。一个典型的方法是将“当前级别”传递给递归函数,然后检查该层是否实际上是一个层,如果是,则将每个子片段传递给递归调用,否则,对该数据进行操作。这可能不会直接翻译成您在这里的确切情况,但是迭代递归结构(我认为会考虑嵌套字典)的答案通常是使用递归,所以这就是我在这里的想法。
  • 喜欢this。同样,不是直接相关,但这是一种经常使用的模式,可能与您的问题相关。

标签: python function dictionary generator iterable


【解决方案1】:

我在看到@VoNWooDSoN 的回答后写了这篇文章。我把它变成了一个迭代器,而不是在函数内部打印,并进行了一些更改以使其更具可读性。所以在这里查看他的original answer

def flatten(d, base=()):
    for k, v in d.items():
        if isinstance(v, dict):
            yield from flatten(v, base + (k,))
        else:
            yield base + (k, v)

1- 产生而不是打印。

2- isinstance() 而不是 type 以便 dict 的子类也可以工作。您还可以使用 typing 模块中的 MutableMapping 而不是 dict 以使其更通用。

3- IMO,从.items() 获取(k, v) 对比kd[k] 更具可读性。

更通用?

您是否想将其扩展为更通用的 CAN(不必像 OP 中的解决方案那样)接受depths 的数量以防万一?

考虑以下示例:

d_level1 = {"a": 1, "b": 2, "c": 3}
d_level2 = {"group_1": {"a": 1}, "group_2": {"b": 2, "c": 3}}
d_level3 = {"collection_1": d_level2}

for items in flatten(d_level3):
    print(items)
print('------------------------------')
for items in flatten(d_level3, depth=0):
    print(items)
print('------------------------------')
for items in flatten(d_level3, depth=1):
    print(items)
print('------------------------------')
for items in flatten(d_level3, depth=2):
    print(items)

输出:

('collection_1', 'group_1', 'a', 1)
('collection_1', 'group_2', 'b', 2)
('collection_1', 'group_2', 'c', 3)
------------------------------
('collection_1', {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}})
------------------------------
('collection_1', 'group_1', {'a': 1})
('collection_1', 'group_2', {'b': 2, 'c': 3})
------------------------------
('collection_1', 'group_1', 'a', 1)
('collection_1', 'group_2', 'b', 2)
('collection_1', 'group_2', 'c', 3)

depth=None 不考虑深度(仍然按照您想要的方式工作)。但是现在通过指定从02 的深度,您可以看到我们能够迭代我们想要的深度。这是代码:

def flatten(d, base=(), depth=None):
    for k, v in d.items():
        if not isinstance(v, dict):
            yield base + (k, v)
        else:
            if depth is None:
                yield from flatten(v, base + (k,))
            else:
                if depth == 0:
                    yield base + (k, v)
                else:
                    yield from flatten(v, base + (k,), depth - 1)

【讨论】:

    【解决方案2】:

    这里有一个快速而肮脏的解决方案:

    d_level1 = {"a":1,"b":2,"c":3}
    d_level2 = {"group_1":{"a":1}, "group_2":{"b":2,"c":3}}
    d_level3 = {"collection_1":d_level2}
    
    def flatten(d_in, base=()):
        for k in d_in:
            if type(d_in[k]) == dict:
                flatten(d_in[k], base+(k,))
            else:
                print(base + (k, d_in[k]))
    
    flatten(d_level1)
    # ('a', 1)
    # ('b', 2)
    # ('c', 3)
    
    flatten(d_level2)
    #('group_1', 'a', 1)
    #('group_2', 'b', 2)
    #('group_2', 'c', 3)
    
    flatten(d_level3)
    # ('collection_1', 'group_1', 'a', 1)
    # ('collection_1', 'group_2', 'b', 2)
    # ('collection_1', 'group_2', 'c', 3)
    

    注意!! Python的递归限制约为1000!因此,在 python 中使用递归时,请仔细考虑您要执行的操作,并准备好在调用这样的递归函数时捕获 RuntimeError。

    编辑: 使用 cmets 我意识到我犯了一个错误,我没有将密钥添加到 level1 dict 输出,并且我使用可变结构作为默认参数。我在打印声明中添加了这些和括号并重新发布。输出现在与 OP 所需的输出相匹配,并使用更好、更现代的 python。

    【讨论】:

    • 参数默认值最好不要设置可变值,d的默认值使用空元组。
    • 它不报告一级字典的键,因为它是一个错误。我会修复它。此外,使用可变集合(列表)作为默认参数是一个坏主意。我也会解决这个问题。等待编辑。
    【解决方案3】:

    试试这个代码

    它还支持关卡组合

    from typing import List, Tuple
    
    
    def iterate_multilevel_dictionary(d: dict):
        dicts_to_iterate: List[Tuple[dict, list]] = [(d, [])]
        '''
        the first item is the dict object and the second object is the prefix keys 
        '''
        while dicts_to_iterate:
            current_dict, suffix = dicts_to_iterate.pop()
            for k, v in current_dict.items():
                if isinstance(v, dict):
                    dicts_to_iterate.append((v, suffix + [k]))
                else:
                    yield suffix + [k] + [v]
    
    
    if __name__ == '__main__':
        d_level1 = {"a": 1, "b": 2, "c": 3}
        print(f"test for {d_level1}")
        for items in iterate_multilevel_dictionary(d_level1):
            print(items)
        d_level2 = {"group_1": {"a": 1}, "group_2": {"b": 2, "c": 3}}
        print(f"test for {d_level2}")
        for items in iterate_multilevel_dictionary(d_level2):
            print(items)
    
        d_level3 = {"collection_1": d_level2}
        print(f"test for {d_level3}")
        for items in iterate_multilevel_dictionary(d_level3):
            print(items)
    
        d_level123 = {}
        [d_level123.update(i) for i in [d_level1, d_level2, d_level3]]
        print(f"test for {d_level123}")
        for items in iterate_multilevel_dictionary(d_level123):
            print(items)
    

    输出是:

    test for {'a': 1, 'b': 2, 'c': 3}
    ['a', 1]
    ['b', 2]
    ['c', 3]
    test for {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}
    ['group_2', 'b', 2]
    ['group_2', 'c', 3]
    ['group_1', 'a', 1]
    test for {'collection_1': {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}}
    ['collection_1', 'group_2', 'b', 2]
    ['collection_1', 'group_2', 'c', 3]
    ['collection_1', 'group_1', 'a', 1]
    test for {'a': 1, 'b': 2, 'c': 3, 'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}, 'collection_1': {'group_1': {'a': 1}, 'group_2': {'b': 2, 'c': 3}}}
    ['a', 1]
    ['b', 2]
    ['c', 3]
    ['collection_1', 'group_2', 'b', 2]
    ['collection_1', 'group_2', 'c', 3]
    ['collection_1', 'group_1', 'a', 1]
    ['group_2', 'b', 2]
    ['group_2', 'c', 3]
    ['group_1', 'a', 1]
    

    使用递归是另一种方法,但我认为不使用递归编写更具挑战性和效率:)

    【讨论】:

      【解决方案4】:

      识别字典深度的线性解决方案。

      d={'a':{1:1,2:2},"b":0,'c':"{}"}
      print(d)
      s=str(d)
      
      dictionary_stack,dictionary_depth=0,0
      def push():
          global dictionary_depth
          global dictionary_stack
          dictionary_stack+=1
          dictionary_depth=max(dictionary_depth,dictionary_stack)
      
      def pop():
          global dictionary_stack
          dictionary_stack-=1
      
      string_safety=False
      for c in s:
          if c =="'":
              string_safety=not(string_safety)
          
          if not(string_safety) and c =='{':
              push()
          
          if not(string_safety) and c =='}':
              pop()
      
      
      print(dictionary_depth)
      

      输出:

      {'a': {1:1, 2:2}, 'b': 0, 'c': '{}'}

      2

      【讨论】:

        猜你喜欢
        • 2013-07-21
        • 1970-01-01
        • 2015-06-03
        • 2017-07-25
        相关资源
        最近更新 更多