递归遍历多维字典,维度未知。

17

我想创建一个函数来递归遍历一个多维字典,其中维度是未知的。

以下是我迄今为止想到的内容,但似乎并没有正确工作。它将会打印出某些键/值两次,并且它们不是按顺序排列的。

def walk_dict(d):
    for k,v in d.items():
        if isinstance(v, dict):
            walk_dict(v)
        else:
            print "%s %s" % (k, v) 

以下是一个示例数组:

d = {
        'plan_code': 'b',
        'quantity': '1',
        'account': {
            'account_code': 'b',
            'username': 'jdoe',
            'email': 'jdoe@domain.com',
            'first_name': 'b',
            'last_name': 'b',
            'company_name': 'Company, LLC.',
            'billing_info': {
                'first_name': 'b',
                'last_name': 'b',
                'address1': '123 Test St',
                'city': 'San Francisco',
                'state': 'CA',
                'country': 'US',
                'zip': '94105',
                'credit_card': {
                    'number': '1',
                    'year': '2018',
                    'month': '12',
                    'verification_value': '123',
                },
            },
        },
    }
9个回答

20

我不确定你的最终目标是什么,但代码正在按照它应该做的去执行。你看到的可能是重复项,因为在'account'和'billing_info'中都存在类似'first_name':'b'这样的键/值组合,而它们都在'account'内部。我不确定你要查找的顺序是什么,但字典是无序的,所以你打印它们的函数需要给它们一些顺序,例如通过替换以下内容:

for k,v in d.items():

使用

for k,v in sorted(d.items(),key=lambda x: x[0]):

或者你需要一个有序字典。你也可以使用pprint模块,以这种方式来漂亮地打印一个字典:

>>> import pprint
>>> pprint.pprint(d)
{'account': {'account_code': 'b',
             'billing_info': {'address1': '123 Test St',
                              'city': 'San Francisco',
                              'country': 'US',
                              'credit_card': {'month': '12',
                                              'number': '1',
                                              'verification_value': '123',
                                              'year': '2018'},
                              'first_name': 'b',
                              'last_name': 'b',
                              'state': 'CA',
                              'zip': '94105'},
             'company_name': 'Company, LLC.',
             'email': 'jdoe@domain.com',
             'first_name': 'b',
             'last_name': 'b',
             'username': 'jdoe'},
 'plan_code': 'b',
 'quantity': '1'}

然而,我并不完全确定你在这里的最终目标是什么。此外,当值是字典时,你缺少键。我修改了你的代码,做了类似于pprint的事情:

def walk_dict(d,depth=0):
    for k,v in sorted(d.items(),key=lambda x: x[0]):
        if isinstance(v, dict):
            print ("  ")*depth + ("%s" % k)
            walk_dict(v,depth+1)
        else:
            print ("  ")*depth + "%s %s" % (k, v) 

对于你的示例字典产生以下结果:

>>> walk_dict(d)
account
  account_code b
  billing_info
    address1 123 Test St
    city San Francisco
    country US
    credit_card
      month 12
      number 1
      verification_value 123
      year 2018
    first_name b
    last_name b
    state CA
    zip 94105
  company_name Company, LLC.
  email jdoe@domain.com
  first_name b
  last_name b
  username jdoe
plan_code b
quantity 1

5
import json
print(json.dumps(d, indent=4))

1
在我的情况下,我需要这样做是因为json.dumps无法处理结构中的某些对象。 - OrangeDog
jsonpickle 库在表示 json 无法处理的对象类型方面表现出色 - 可以尝试使用它。 - Chris Johnson

2

这确实可以正确地打印键值对。你能指出哪些数据会重复吗?基于上述数据,可能会存在混淆,因为这些键:

'first_name': 'b',
'last_name': 'b',

这些内容是两个字典 - 'account' 和 'billing_info' 的一部分。因此它们将在输出中出现两次。

如果您希望以某种顺序打印字典的键和值,请使用有序字典。


我刚刚拿到那个字典,没有注意到重复的部分。以为自己做错了什么。谢谢你指出来。 - imns

2
这是对Justin Peel的已接受答案进行改进的一种变化,它将其结果作为一个OrderedDict返回,而不是打印结果。
from collections import OrderedDict

def sort_by_keys(dct,):
    new_dct = OrderedDict({})
    for item in sorted(dct.items(), key=lambda (key, val): key):
        key = item[0]
        val = item[1]
        if isinstance(val, dict):
            new_dct[key] = sort_by_keys(val)
        else:
            new_dct[key] = val
    return new_dct

2
Python 3 不支持元组解包。 - Jenobi

1

正如Justin Peel 提到的那样, pprint.pprint 可能会符合您的要求.

我认为您代码的问题在于在递归之前应先打印出键,也就是说,请更改

    if isinstance(v, dict):
        walk_dict(v)

    if isinstance(v, dict):
        print k
        walk_dict(v)

不过无论如何,如果你不添加缩进等内容,它看起来都会相当混乱。

这种东西实际上相当复杂;如果你想得到一些想法,请查看pprint的代码。


0

这里有一个示例,它还可以将任意对象转换为字典(如果Python可以的话):

from typing import Union

def walk_dict(d, depth=0) -> None:
    for k, v in sorted(d.items(), key=lambda x: x[0]):
        indentation = depth * "  "
        if not isinstance(v, Union[int, list, None, str]):
            if isinstance(v, dict):
                print(f"k={k}")
                walk_dict(v, depth + 1)
            elif isinstance(v.__dict__, dict):
                print(f"k={k}")
                walk_dict(v.__dict__, depth + 1)
        else:
            print(f"{indentation}k={k},  v={v}")


d = {
    "plan_code": "b",
    "quantity": "1",
    "account": {
        "account_code": "b",
        "username": "jdoe",
        "email": "jdoe@domain.com",
        "first_name": "b",
        "last_name": "b",
        "company_name": "Company, LLC.",
        "billing_info": {
            "first_name": "b",
            "last_name": "b",
            "address1": "123 Test St",
            "city": "San Francisco",
            "state": "CA",
            "country": "US",
            "zip": "94105",
            "credit_card": {
                "number": "1",
                "year": "2018",
                "month": "12",
                "verification_value": "123",
            },
        },
    },
}


walk_dict(d)

输出:

k=account
  k=account_code,  v=b
k=billing_info
    k=address1,  v=123 Test St
    k=city,  v=San Francisco
    k=country,  v=US
k=credit_card
      k=month,  v=12
      k=number,  v=1
      k=verification_value,  v=123
      k=year,  v=2018
    k=first_name,  v=b
    k=last_name,  v=b
    k=state,  v=CA
    k=zip,  v=94105
  k=company_name,  v=Company, LLC.
  k=email,  v=jdoe@domain.com
  k=first_name,  v=b
  k=last_name,  v=b
  k=username,  v=jdoe
k=plan_code,  v=b
k=quantity,  v=1

0
在Python中,字典是由键索引的。键可以是任何不可变类型,如字符串或数字。除非键已排序,否则它们将始终以任意顺序返回。因此,您的walk_dict正在打印看似随机的结果。
这里是一个walk_dict的示例,它打印所有键和值。我已经在每个字典级别上对键进行了排序。此外,我打印了每个键。在递归之前,您的代码没有打印键。最后,我添加了字符串填充以强调字典的每个级别。所有的doctest都通过了。我希望这能帮助您构建最终函数。
import doctest


def walk_dict(seq, level=0):
    """Recursively traverse a multidimensional dictionary and print all
    keys and values.

    >>> d = {'dog': 'dusty', 'cat': 'fluffy', 'bird': 'chirpy'}
    >>> walk_dict(d)
    bird chirpy
    cat fluffy
    dog dusty
    >>> d = {'location': 'home', 'animals':{'dog': 'dusty', 'cat': 'fluffy', 'bird': 'chirpy'}}
    >>> walk_dict(d)
    animals
      bird chirpy
      cat fluffy
      dog dusty
    location home
    >>> d = {'location': 'home', 'animals':{'dog': 'dusty', 'cat': 'fluffy', 'bird': {'name':'chirpy', 'color':'blue'}}}
    >>> walk_dict(d)
    animals
      bird
        color blue
        name chirpy
      cat fluffy
      dog dusty
    location home
    >>> d = { \
            'plan_code': 'b', \
            'quantity': '1', \
            'account': { \
                'account_code': 'b', \
                'username': 'jdoe', \
                'email': 'jdoe@domain.com', \
                'first_name': 'b', \
                'last_name': 'b', \
                'company_name': 'Company, LLC.', \
                'billing_info': { \
                    'first_name': 'b', \
                    'last_name': 'b', \
                    'address1': '123 Test St', \
                    'city': 'San Francisco', \
                    'state': 'CA', \
                    'country': 'US', \
                    'zip': '94105', \
                    'credit_card': { \
                        'number': '1', \
                        'year': '2018', \
                        'month': '12', \
                        'verification_value': '123', \
                    }, \
                }, \
            }, \
        } 
    >>> walk_dict(d)
    account
      account_code b
      billing_info
        address1 123 Test St
        city San Francisco
        country US
        credit_card
          month 12
          number 1
          verification_value 123
          year 2018
        first_name b
        last_name b
        state CA
        zip 94105
      company_name Company, LLC.
      email jdoe@domain.com
      first_name b
      last_name b
      username jdoe
    plan_code b
    quantity 1
    """
    items = seq.items()
    items.sort()
    for v in items:
        if isinstance(v[1], dict):
            # Print the key before make a recursive call
            print "%s%s" % ("  " * level, v[0])
            nextlevel = level + 1
            walk_dict(v[1], nextlevel)
        else:
            print "%s%s %s" % ("  " * level, v[0], v[1])


if __name__ == '__main__':
    doctest.testmod()

0

你的代码非常好,它确切地按照你所告诉它的做。

我在输出中看到的唯一重复的是 first_name 和 last_name,这些实际上在不同的字典中被定义了两次。

至于“无序”,这是一个字典,它没有保证的顺序。也许它有,但它将基于内部表示,您不应该依赖它。

你需要做的是按你想要的方式对数据进行排序。你还可以打印出子字典的键值,以使输出更容易理解。


0
到目前为止,提到的解决方案都没有做到我想要的,但是根据 Justin Peel 的详细版本,我能够制作出按照我想要的方式打印的东西:
def dict_parser(data, depth=0):
    if isinstance(data, dict):
        for key in data:
            value = data[key]
            print('  ') * depth + '"' + key + '":'
            dict_parser(value, depth + 1)
    elif isinstance(data, list):
        for item in data:
            if isinstance(item, list):
                dict_parser(item, depth + 1)
            else:
                print('  ') * depth + '"' + item + '"'
    elif isinstance(data, bool):
        print('  ') * depth + str(data)
    else:
        print('  ') * depth + '"' + data + '"'

输出的内容类似于:

"saved_skels":
  "E:\Child_Skeleton_6ft.fbx"
  "E:\Female_Skeleton_6ft.fbx"
  "E:\HeroFemale_Skeleton.fbx"
  "E:\HeroMale_Skeleton.fbx"
"saved_roots":
  "E:\users\Characters"
  "E:\content\characters"
"saved_tabs":
  "Characters":
    "ProxyFemale":
      "HeroFemale_Skeleton.fbx"
    "animation":
      "HeroFemale_Skeleton.fbx"
    "ProxyMale":
      "HeroMale_Skeleton.fbx"
  "Game":
    ""
  "Proto":
    "Female":
      "Female_Skeleton_NewBP.fbx"
  "Assets":
    ""
  "characters":
    "player":
      "player_Skeleton.fbx"
    "actor":
      "Hero_Skeleton.fbx"
"edit_common_checkbox_state":
  False
"get_latest_checkbox_state":
  True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接