遍历所有嵌套的字典值?


120
for k, v in d.iteritems():
    if type(v) is dict:
        for t, c in v.iteritems():
            print "{0} : {1}".format(t, c)

我试图遍历字典并打印出所有值不是嵌套字典的键值对。如果值是字典,我想进入它并打印出它的键值对...等等。有什么帮助吗?

编辑

这个怎么样?它仍然只打印一件事。

def printDict(d):
    for k, v in d.iteritems():
        if type(v) is dict:
            printDict(v)
        else:
            print "{0} : {1}".format(k, v)

完整的测试用例

字典:

{u'xml': {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'},
      u'port': u'11'}}

结果:

xml : {u'config': {u'portstatus': {u'status': u'good'}, u'target': u'1'}, u'port': u'11'}

1
听起来像您想要递归,但是描述不够清楚,无法确定。那么一些示例输入/输出呢?另外,您的代码有什么问题?
Niklas B.


2
@ Jan-PhilipGehrcke:在没有递归的情况下在树状数据结构上实现算法是很容易自杀的。
Niklas B.

2
@Takkun:您正在使用dict作为变量名。永远不要这样做(这就是失败的原因)。
Niklas B.

3
@NiklasB。,回复:“自杀”:我刚刚实现了Scharron算法的迭代版本,其算法仅长了两行,而且仍然很容易理解。此外,从树到普通图时,通常需要将递归转换为迭代。
弗雷德·富

Answers:


157

如Niklas所说,您需要递归,即您想定义一个函数来打印您的字典,如果该值是一个字典,则想使用这个新字典来调用您的打印函数。

就像是 :

def myprint(d):
    for k, v in d.items():
        if isinstance(v, dict):
            myprint(v)
        else:
            print("{0} : {1}".format(k, v))

3
小改进。在调用myprint(v)之前添加print(k)。
Naomi Fridman

进行递归很简单。
sergzach

36

如果您编写自己的递归实现或带有堆栈的迭代等效项,则可能会出现问题。请参阅以下示例:

    dic = {}
    dic["key1"] = {}
    dic["key1"]["key1.1"] = "value1"
    dic["key2"]  = {}
    dic["key2"]["key2.1"] = "value2"
    dic["key2"]["key2.2"] = dic["key1"]
    dic["key2"]["key2.3"] = dic

在正常情况下,嵌套字典将是像数据结构一样的n元树。但是定义不排除出现交叉边缘甚至后边缘的可能性(因此不再是树)。例如,这里key2.2key1保留到字典,key2.3指向整个字典(后沿/循环)。当有后沿(循环)时,堆栈/递归将无限运行。

                          root<-------back edge
                        /      \           |
                     _key1   __key2__      |
                    /       /   \    \     |
               |->key1.1 key2.1 key2.2 key2.3
               |   /       |      |
               | value1  value2   |
               |                  | 
              cross edge----------|

如果您使用Scharron的此实现打印此词典

    def myprint(d):
      for k, v in d.items():
        if isinstance(v, dict):
          myprint(v)
        else:
          print "{0} : {1}".format(k, v)

您会看到此错误:

    RuntimeError: maximum recursion depth exceeded while calling a Python object

senderle的实现也是如此

同样,您可以从Fred Foo的此实现中获得无限循环:

    def myprint(d):
        stack = list(d.items())
        while stack:
            k, v = stack.pop()
            if isinstance(v, dict):
                stack.extend(v.items())
            else:
                print("%s: %s" % (k, v))

但是,Python实际上会检测嵌套字典中的循环:

    print dic
    {'key2': {'key2.1': 'value2', 'key2.3': {...}, 
       'key2.2': {'key1.1': 'value1'}}, 'key1': {'key1.1': 'value1'}}

“ {...}”是检测到循环的位置。

根据Moondra的要求,这是一种避免循环(DFS)的方法:

def myprint(d): 
  stack = list(d.items()) 
  visited = set() 
  while stack: 
    k, v = stack.pop() 
    if isinstance(v, dict): 
      if k not in visited: 
        stack.extend(v.items()) 
      else: 
        print("%s: %s" % (k, v)) 
      visited.add(k)

那么您将如何实现迭代解决方案?
dreftymac

2
@dreftymac我会为密钥添加一个访问集,以避免循环:def myprint(d): stack = d.items() visited = set() while stack: k, v = stack.pop() if isinstance(v, dict): if k not in visited: stack.extend(v.iteritems()) else: print("%s: %s" % (k, v)) visited.add(k)
tengr '17

1
感谢您指出了这一点。您介意将代码包含在答案中吗?我认为它可以很好地回答您。
Moondra

对于Python3,使用list(d.items())作为d.items()返回一个视图,而不是一个列表,并使用v.items()替代v.iteritems()
马克斯

33

由于a dict是可迭代的,因此您只需稍作一些更改就可以将经典的嵌套容器可迭代公式应用于此问题。这是Python 2版本(请参阅下面的3):

import collections
def nested_dict_iter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in nested_dict_iter(value):
                yield inner_key, inner_value
        else:
            yield key, value

测试:

list(nested_dict_iter({'a':{'b':{'c':1, 'd':2}, 
                            'e':{'f':3, 'g':4}}, 
                       'h':{'i':5, 'j':6}}))
# output: [('g', 4), ('f', 3), ('c', 1), ('d', 2), ('i', 5), ('j', 6)]

在Python 2中,可能可以创建一个Mapping限定为,Mapping但不包含的自定义iteritems,在这种情况下,这将失败。文档没有指出这iteritems是必需的Mapping;另一方面,Mapping类型提供了一种iteritems方法。因此,对于custom Mappings,从collections.Mapping显式继承以防万一。

在Python 3中,有许多改进。从Python 3.3开始,抽象基类存在于中collections.abc。它们也保持collections向后兼容,但是将我们的抽象基类放在一个命名空间中会更好。因此,这是abc从导入的collections。Python 3.3还添加了yield from,它专门用于这种情况。这不是空的语法糖。它可能导致更快的代码和与协同程序更明智的交互。

from collections import abc
def nested_dict_iter(nested):
    for key, value in nested.items():
        if isinstance(value, abc.Mapping):
            yield from nested_dict_iter(value)
        else:
            yield key, value

3
isinstance(item, collections.Iterable)不保证hasattr(item, "iteritems")。检查collections.Mapping更好。
Fred Foo 2012年

1
@larsmans,您当然是对的。我本以为使用Iterable可以使该解决方案更加通用,却忘记了,显然,可迭代对象不一定具有iteritems
senderle

对此答案+1,因为它是解决此问题的通用解决方案,但不仅限于打印值。@Takkun你绝对应该考虑这个选项。从长远来看,您将不仅仅是打印值。
亚历杭德罗·皮亚德

1
@ Seanny123,感谢您引起我的注意。实际上,Python 3通过两种方式更改了图片-我将使用新yield from语法将其重写为一个版本。
senderle '17

25

替代迭代解决方案:

def myprint(d):
    stack = d.items()
    while stack:
        k, v = stack.pop()
        if isinstance(v, dict):
            stack.extend(v.iteritems())
        else:
            print("%s: %s" % (k, v))

2
是的,这就是我想象的样子。谢谢。因此,这样做的好处是它不会为极深的嵌套溢出堆栈?还是还有其他东西?
Niklas B.

@NiklasB .:是的,这是第一个好处。同样,通过用lista deque或什至优先级队列替换堆栈(a ),可以很容易地将此​​版本适应于不同的遍历顺序。
弗雷德·富

是的,很有道理。谢谢您,
并祝

是的,但是此解决方案比我的和递归解决方案更占用空间。
schlamar 2012年

1
@ ms4py:为了娱乐,我创建了一个基准。在我的计算机上,所有三个测试字典的递归版本是最快的,而larsmans是第二。如预期的那样,使用生成器的版本相对较慢(因为它必须对不同的生成器上下文进行大量处理)
Niklas B.

9

我写的版本略有不同,跟踪到达那里的过程中的按键

def print_dict(v, prefix=''):
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            print_dict(v2, p2)
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            print_dict(v2, p2)
    else:
        print('{} = {}'.format(prefix, repr(v)))

在您的数据上,它将打印

data['xml']['config']['portstatus']['status'] = u'good'
data['xml']['config']['target'] = u'1'
data['xml']['port'] = u'11'

修改它以将前缀作为键的元组而不是字符串来跟踪前缀(如果您需要的话)也很容易。


如何将输出添加到列表?
Shash

5

这是pythonic的方法。此功能将允许您在所有级别中遍历键值对。它不会将整个内容保存到内存中,而是在您遍历字典时逐步执行

def recursive_items(dictionary):
    for key, value in dictionary.items():
        if type(value) is dict:
            yield (key, value)
            yield from recursive_items(value)
        else:
            yield (key, value)

a = {'a': {1: {1: 2, 3: 4}, 2: {5: 6}}}

for key, value in recursive_items(a):
    print(key, value)

版画

a {1: {1: 2, 3: 4}, 2: {5: 6}}
1 {1: 2, 3: 4}
1 2
3 4
2 {5: 6}
5 6

2

迭代解决方案作为替代方案:

def traverse_nested_dict(d):
    iters = [d.iteritems()]

    while iters:
        it = iters.pop()
        try:
            k, v = it.next()
        except StopIteration:
            continue

        iters.append(it)

        if isinstance(v, dict):
            iters.append(v.iteritems())
        else:
            yield k, v


d = {"a": 1, "b": 2, "c": {"d": 3, "e": {"f": 4}}}
for k, v in traverse_nested_dict(d):
    print k, v

那个怎么样?Big O应该是相同的(O(depth)适用于递归解决方案。如果我考虑正确的话,此版本也应如此)。
Niklas B.

“复制堆栈”?你在说什么?每个函数调用都会创建一个新的堆栈框架。您的解决方案iters用作显式堆栈,因此Big-O内存消耗是相同的,还是我丢失了某些东西?
Niklas B.

@NiklasB。递归总是伴随着开销,有关详细信息,请参阅Wikipedia的此部分:en.wikipedia.org/wiki/…递归解决方案的堆栈框架要大得多。
schlamar 2012年

您一定会误会该段。它没有说什么来支持您的陈述。
Niklas B.

1
@NiklasB。不,因为这里的堆栈框架只是迭代器,对于递归解决方案,堆栈框架具有迭代器,程序计数器,可变环境等...
schlamar 2012年

2

基于Scharron解决方案的另一种使用列表的解决方案

def myprint(d):
    my_list = d.iteritems() if isinstance(d, dict) else enumerate(d)

    for k, v in my_list:
        if isinstance(v, dict) or isinstance(v, list):
            myprint(v)
        else:
            print u"{0} : {1}".format(k, v)

2

考虑到该值可能是包含字典的列表,我正在使用以下代码来打印嵌套字典的所有值。当我将JSON文件解析为字典并且需要快速检查其任何值是否为时,这对我很有用None

    d = {
            "user": 10,
            "time": "2017-03-15T14:02:49.301000",
            "metadata": [
                {"foo": "bar"},
                "some_string"
            ]
        }


    def print_nested(d):
        if isinstance(d, dict):
            for k, v in d.items():
                print_nested(v)
        elif hasattr(d, '__iter__') and not isinstance(d, str):
            for item in d:
                print_nested(item)
        elif isinstance(d, str):
            print(d)

        else:
            print(d)

    print_nested(d)

输出:

    10
    2017-03-15T14:02:49.301000
    bar
    some_string

我在这里有很多类似的问题stackoverflow.com/questions/50642922/…。有没有一种方法可以找到字典列表的最后一个元素,删除该元素,然后再将其上移?如果不删除,我想创建一个列表,其中最后一个元素是数据的深度,所以我将列表反向并删除
Heenashree Khandelwal

1

这是Fred Foo对Python 2的回答的修改版本。在原始响应中,仅输出最深层的嵌套。如果将键输出为列表,则可以保留所有级别的键,尽管要引用它们,则需要引用列表。

功能如下:

def NestIter(nested):
    for key, value in nested.iteritems():
        if isinstance(value, collections.Mapping):
            for inner_key, inner_value in NestIter(value):
                yield [key, inner_key], inner_value
        else:
            yield [key],value

引用键:

for keys, vals in mynested: 
    print(mynested[keys[0]][keys[1][0]][keys[1][1][0]])

三级字典。

您需要在访问多个键之前知道级别的数量,并且级别的数量应该是恒定的(在遍历值时可以添加一小段脚本来检查嵌套级别的数量,但是我没有还没看这个)。


1

我发现这种方法更加灵活,这里您仅提供生成器函数,该函数可以生成键,值对,并且可以轻松扩展以遍历列表。

def traverse(value, key=None):
    if isinstance(value, dict):
        for k, v in value.items():
            yield from traverse(v, k)
    else:
        yield key, value

然后,您可以编写自己的myprint函数,然后打印这些键值对。

def myprint(d):
    for k, v in traverse(d):
        print(f"{k} : {v}")

一个测试:

myprint({
    'xml': {
        'config': {
            'portstatus': {
                'status': 'good',
            },
            'target': '1',
        },
        'port': '11',
    },
})

输出:

status : good
target : 1
port : 11

我在Python 3.6上进行了测试。


0

这些答案仅适用于2级子词典。有关更多信息,请尝试以下方法:

nested_dict = {'dictA': {'key_1': 'value_1', 'key_1A': 'value_1A','key_1Asub1': {'Asub1': 'Asub1_val', 'sub_subA1': {'sub_subA1_key':'sub_subA1_val'}}},
                'dictB': {'key_2': 'value_2'},
                1: {'key_3': 'value_3', 'key_3A': 'value_3A'}}

def print_dict(dictionary):
    dictionary_array = [dictionary]
    for sub_dictionary in dictionary_array:
        if type(sub_dictionary) is dict:
            for key, value in sub_dictionary.items():
                print("key=", key)
                print("value", value)
                if type(value) is dict:
                    dictionary_array.append(value)



print_dict(nested_dict)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.