简体   繁体   English

如何在 Python 中迭代 N 级嵌套字典?

[英]How to iterate through an N-level nested dictionary in Python?

I find myself making multilevel dictionaries quite a bit.我发现自己制作多级词典相当多。 I always have to write very verbose code to iterate through all the levels of the dictionaries with a lot of temporary variables.我总是不得不编写非常冗长的代码来遍历包含大量临时变量的字典的所有级别。

Is there a way to generalize this function to iterate through multiple levels instead of hardcoding in and manually specifying how many levels there are?有没有办法概括这个函数来迭代多个级别而不是硬编码并手动指定有多少级别?

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)

I've written this after I saw @VoNWooDSoN's answer.我是在看到@VoNWooDSoN 的回答后写的。 I turned it into an iterator instead of printing inside the function and a little bit of changes to make it more readable.我把它变成了一个迭代器,而不是在函数内部打印,并进行了一些更改以使其更具可读性。 So see his original answer here.所以在这里看到他的原始答案

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- yielding instead of printing. 1-产量而不是印刷。

2- isinstance() instead of type so that subclasses of dict can also work. 2- isinstance()而不是type以便dict子类也可以工作。 You could also use MutableMapping from typing module instead of dict to make it more generic.您还可以使用来自typing模块的MutableMapping而不是dict使其更通用。

3- IMO , getting (k, v) pairs from .items() is much more readable than k and d[k] . 3- IMO,从.items()获取(k, v) .items() kd[k]更具可读性。

More generic ?更通用?

Do you wanna expand this to even more generic which CAN (not have to, like the solution in the OP) accept the number of depths just in case?您是否想将其扩展为更通用的CAN (不必像 OP 中的解决方案那样)接受depths数以防万一?

Consider these examples:考虑以下示例:

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)

output:输出:

('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 doesn't consider the depth (still works like you want at the first place). depth=None不考虑深度(仍然像你想要的那样工作)。 But now by specifying depths from 0 to 2 you can see that we are able to iterate how deep we want.但是现在通过指定从02深度,您可以看到我们能够迭代我们想要的深度。 here is the code:这是代码:

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)

Here's a quick and dirty solution for you:这是一个快速而肮脏的解决方案:

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)

Be aware!!意识到!! Python has a recursion limit of about 1000! Python 的递归限制约为 1000! So, when using recursion in python think very carefully what you're trying to do and be prepared to catch a RuntimeError if you call a recursive function like this.因此,在 python 中使用递归时,请仔细考虑您要尝试做什么,并准备好在调用这样的递归函数时捕获 RuntimeError。

EDIT: With comments I realized that I'd made a mistake where I did not add the key to the level1 dict output and that I was using a mutable structure as a default argument.编辑:通过评论,我意识到我犯了一个错误,我没有将密钥添加到 level1 dict 输出中,并且我使用的是可变结构作为默认参数。 I added these and parens in the print statement and reposted.我在打印声明中添加了这些和括号并重新发布。 The output now matches the OP's desired output and uses better and modern python.输出现在与 OP 所需的输出相匹配,并使用更好的现代 Python。

try out this code试试这个代码

it also supports a combination of levels它还支持级别的组合

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)

the outputs is:输出是:

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]

using recursion is another approach but I thought writing without recursion is more challenging and more efficient :)使用递归是另一种方法,但我认为没有递归的写作更具挑战性和效率:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM