关于python:遍历所有嵌套字典值?

Loop through all nested dictionary values?

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

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

编辑

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

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

完整测试用例

字典:

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

结果:

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


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

比如:

1
2
3
4
5
6
def myprint(d):
  for k, v in d.iteritems():
    if isinstance(v, dict):
      myprint(v)
    else:
      print"{0} : {1}".format(k, v)

或者对于python 3之后的版本:

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


由于dict是可Iterable的,所以您可以将经典的嵌套容器可Iterable公式应用于这个问题,只需做一些小的更改。下面是一个python 2版本(参见下面的3):

1
2
3
4
5
6
7
8
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

测试:

1
2
3
4
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可以作为Mapping,但不包含iteritems,在这种情况下,这将失败。文件没有说明Mapping需要iteritems;另一方面,来源给Mapping类型一个iteritems方法。因此,对于习惯上的Mappings,为了以防万一,明确地从collections.Mapping继承。

在python 3中,有许多需要改进的地方。从python 3.3开始,抽象基类就生活在collections.abc中。为了向后兼容,它们也保留在collections中,但是在一个名称空间中将抽象基类放在一起更好。所以这是从collections进口abc。python 3.3还添加了yield from,它仅针对这些情况而设计。这并不是空的语法糖分;它可能会导致更快的代码和与协程的更明智的交互。

1
2
3
4
5
6
7
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


如果编写自己的递归实现或与堆栈等效的迭代实现,则存在潜在的问题。请参见此示例:

1
2
3
4
5
6
7
    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.2保存着从key1到key2.3的字典指向整个字典(后缘/循环)。当存在后缘(循环)时,堆栈/递归将无限运行。

1
2
3
4
5
6
7
8
9
                          root<-------back edge
                        /      \           |
                     _key1   __key2__      |
                    /       /   \    \     |
               |->key1.1 key2.1 key2.2 key2.3
               |   /       |      |
               | value1  value2   |
               |                  |
              cross edge----------|

如果用scharron的这个实现打印这个字典

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

您将看到此错误:

1
    RuntimeError: maximum recursion depth exceeded while calling a Python object

来自senderle的实现也是如此。

类似地,您可以从fred foo获得一个无限循环的实现:

1
2
3
4
5
6
7
8
    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实际上在嵌套字典中检测循环:

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

"…"是检测循环的位置。

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

1
2
3
4
5
6
7
8
9
10
11
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)


替代迭代解:

1
2
3
4
5
6
7
8
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))


我写的版本稍有不同,在到达那里的过程中一直跟踪着钥匙。

1
2
3
4
5
6
7
8
9
10
11
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)))

在你的数据上,它会打印出来

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

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


这是一种用Python做的方法。此函数允许您循环访问所有级别中的键值对。它不会将整个内容保存到内存中,而是在循环访问时遍历dict。

1
2
3
4
5
6
7
8
9
10
11
12
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)

印刷品

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    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)

输出:

1
2
3
4
    10
    2017-03-15T14:02:49.301000
    bar
    some_string


下面是FredFoo对python 2的答案的修改版本。在原始响应中,只输出嵌套的最深级别。如果将键输出为列表,则可以保留所有级别的键,但要引用它们,需要引用列表列表。

功能如下:

1
2
3
4
5
6
7
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

要引用键:

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

三级词典。

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


基于Scharron解决方案的列表替代解决方案

1
2
3
4
5
6
7
8
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)

作为替代方案的迭代解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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


我发现这种方法有点灵活,这里您只提供了一个生成器函数,它发出键、值对,并且可以很容易地扩展到对列表进行迭代。

1
2
3
4
5
6
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函数,然后打印这些键值对。

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

测试:

1
2
3
4
5
6
7
8
9
10
11
myprint({
    'xml': {
        'config': {
            'portstatus': {
                'status': 'good',
            },
            'target': '1',
        },
        'port': '11',
    },
})

输出:

1
2
3
status : good
target : 1
port : 11

我在Python3.6上测试过这个。