python获取字典末端value值的取值方式

前言

实践过程可不看,末尾直接是答案!

一、需求描述

写一段代码,要求输入任意一个字典(可有其他参数输入),无论它是什么样的结构,都能通过该段代码获取其取值路径,例如字典:

test_data = {
    "a":{"b":1},
    "c":[2,{"d":3}],
    "e":"success",
}

需要输出:

test_data["a"]["b"]
test_data["c"][0]
test_data["c"][1]["d"]
test_data["e"]

二、方案分析

1、这是一个拆解字典的过程,很容易想到使用遍历dict.items()的方法进行处理

2、字典可包含python所有数据类型,如字典、列表、元组、字符串等等

3、根据需求描述,没有对数据类型进行限制,因此这里只对Json格式数据类型进行处理

4、这里先实现需求的部分功能(后续继续优化),限制数据类型范围为:字典、列表和其他(如字符串、数字等无法拆分的末端值)

5、这里需要对路径进行循环利用,如果一个字典深度很长,那么在循环处理时,就得不断使用for来不断解包,感觉递归更好一些

6、递归的时候,如果一开始解压的是字典,那么当遇到列表的情况,可能就无法递归了

7、感觉这问题没法解决了呀?我相信只要是有逻辑的东西,就一定可以通过代码实现!先动手试试吧!

三、实践过程

1、采用递归方式,同时递归字典或列表:版本1

def get_value_path(data):
    '''递归一个序列(传入列表或字典),如果列表中的元素不是字典或列表,则输出;如果是列表,则继续递归,直到全部输出非字典或列表的元素'''
    if isinstance(data, list):
        for index,value in enumerate(data):
            get_value_path(value)
    elif isinstance(data, dict):
        for key,value in data.items():
            get_value_path(value)
    else:
        yield data

if __name__ == "__main__":

    test_data = {
        "a": {"b": 1},
        "c": [2, {"d": 3}],
        "e": "success",
    }
    for value in get_value_path(test_data):
        print(value)

(1)实践结果:

最终的输出为空,即没有任何输出

(2)案例分析:

①程序逻辑应该简单明了,即判断传入数据是列表或字典则继续递归,不是则输出

②上述没有输出,是因为假设第一次解压的是字典,第二次解压的是列表,意味着在两个不同的for循环中切换,但程序是顺序执行的,因此在一个循环中就已经遍历了所有,以至于没有输出,过程如下:

 

 (3)思考:

应该需要在for循环中,使用yield继续输出

2、采用递归方式,同时递归字典或列表:版本2

def get_value_path(data):
    '''递归一个序列(传入列表或字典),如果列表中的元素不是字典或列表,则输出;如果是列表,则继续递归,直到全部输出非字典或列表的元素'''
    if isinstance(data, list):
        for index,value in enumerate(data):
            if isinstance(value, list):
                get_value_path(value)
            elif isinstance(value, dict):
                get_value_path(value)
            else:
                yield value
    elif isinstance(data, dict):
        for key,value in data.items():
            if isinstance(value, list):
                get_value_path(value)
            elif isinstance(value, dict):
                get_value_path(value)
            else:
                yield value
    else:
        yield data

if __name__ == "__main__":

    test_data = {
        "a": {"b": 1},
        "c": [2, {"d": 3}],
        "e": "success",
    }
    for value in get_value_path(test_data):
        print(value)

(1)实践结果:

只输出了一个值:success

 (2)案例分析:

 (3)思考:

这是为什么呢?这个程序只对一级包做了处理,没法处理到二级包?整个解包过程确实是对一级包做的处理,因为for循环在这里递归的时候,数据取的就是一级包的数据(即使将value全部更换为data),实验如下:

那要想解包到第二层,该怎么做呢?

 3、采用递归方式,同时递归字典或列表:版本3

def get_value_path(data):
    '''递归一个序列(传入列表或字典),如果列表中的元素不是字典或列表,则输出;如果是列表,则继续递归,直到全部输出非字典或列表的元素'''
    if isinstance(data, list):
        for index,data in enumerate(data):
            if isinstance(data, list):
                for index, data in enumerate(data):
                    if isinstance(data, list):
                        get_value_path(data)
                    elif isinstance(data, dict):
                        get_value_path(data)
                    else:
                        yield data
                # get_value_path(data)
            elif isinstance(data, dict):
                for key, data in data.items():
                    if isinstance(data, list):
                        get_value_path(data)
                    elif isinstance(data, dict):
                        get_value_path(data)
                    else:
                        yield data
                # get_value_path(data)
            else:
                yield data
    elif isinstance(data, dict):
        for key,data in data.items():
            if isinstance(data, list):
                for index, data in enumerate(data):
                    if isinstance(data, list):
                        get_value_path(data)
                    elif isinstance(data, dict):
                        get_value_path(data)
                    else:
                        yield data
                # get_value_path(data)
            elif isinstance(data, dict):
                for key, data in data.items():
                    if isinstance(data, list):
                        get_value_path(data)
                    elif isinstance(data, dict):
                        get_value_path(data)
                    else:
                        yield data
                # get_value_path(data)
            else:
                yield data
    else:
        yield data

if __name__ == "__main__":

    test_data = {
        "a": {"b": 1},
        "c": [2, {"d": 3}],
        "e": "success",
        "f": "fail",
    }
    for value in get_value_path(test_data):
        print(value)

(1)实践结果:

采用嵌套的方式,能解到第二层:

(2)案例分析:

这样的方式,从结果看起来可行,但是如果要解包很多层,代码就只剩下复制粘贴了,只是,这样的世界何时是个尽头?

(3)思考:

能否先知道这个字典的深度,再通过生成函数的方式,以eval()或exec()实现整段代码的运行?这样做相当麻烦,但貌似是个可行的思路。

 4、采用递归方式,同时递归字典或列表:版本4

min_def = '''
def get_value_path(data):
    data = data
    if isinstance(data, list):
        for index,data in enumerate(data):
            if isinstance(data, list):
                get_value_path(data)
            elif isinstance(data, dict):
                get_value_path(data)
            else:
                yield data
    elif isinstance(data, dict):
        for key,data in data.items():
            if isinstance(data, list):
                get_value_path(data)
            elif isinstance(data, dict):
                get_value_path(data)
            else:
                yield data
    else:
        yield data

test_data = {
    "a": {"b": 1},
    "c": [2, {"d": 3}],
    "e": "success",
    "f": "fail",
}
for value in get_value_path(test_data):
    print(value)
'''

if __name__ == "__main__":
    exec(min_def)

(1)实践结果:

 (2)案例分析:

通过这种方式是可以运行的,接下来就是对min_def参数化,以达到想要的结果。

 (3)思考:

可能需要解决的问题有:python代码格式化问题(空格)、参数化问题、插入代码片段问题等等。

 5、采用递归方式,同时递归字典或列表:版本5

# 数据格式:
# 替换单元1
replace_list='''
for index, data in enumerate(data):
    if isinstance(data, list):
        {list}
    elif isinstance(data, dict):
        {dict}
    else:
        yield data
'''
# 替换单元2:
replace_dict='''
for key,data in data.items():
    if isinstance(data, list):
        {list}
    elif isinstance(data, dict):
        {dict}
    else:
        yield data
'''
# 替换单元3:
replace_end = '''
get_value_path(data)
'''
# 最小单元:
min_def = '''
def get_value_path(data):
    data = data
    if isinstance(data, list):
        for index,data in enumerate(data):
            if isinstance(data, list):
                {list}
            elif isinstance(data, dict):
                {dict}
            else:
                yield data
    elif isinstance(data, dict):
        for key,data in data.items():
            if isinstance(data, list):
                {list}
            elif isinstance(data, dict):
                {dict}
            else:
                yield data
    else:
        yield data

for value in get_value_path(test_data):
    print(value)
'''

def add_spaces(str_src, n=0):
    line = str_src.splitlines()
    for index, l in enumerate(line):
        line[index] = " "*n + line[index]
    str_add = "\n".join(line)
    return str_add

def base_point(str_src):
    line = str_src.splitlines()
    point = ""
    for index, l in enumerate(line):
        if "{list}" in l:
            point = l.find("{list}")
            break
    return point

# n为dict的层数
def main(data,n=10):
    # 变量test_data固定不变,因为min_def中会使用
    test_data = data
    # min_def的最终函数字符串
    min_def_get = min_def
    for i in range(n):
        if i == n-1:
            p = base_point(min_def_get)
            str_a = str_b = add_spaces(replace_end, n=p)
            def_add = min_def_get.format(list=str_a, dict=str_b)
            min_def_get = def_add
        else:
            p = base_point(min_def_get)
            str_a = add_spaces(replace_list, n=p)
            str_b = add_spaces(replace_dict, n=p)
            def_add = min_def_get.format(list=str_a, dict=str_b)
            min_def_get = def_add
    # print(min_def_get)
    exec(min_def_get)

if __name__ == "__main__":
    test_data01 = {
        "a": {"b": 1},
        "c": [2, {"d": 3},[4,5,6,7,8,9]],
        "e": "success",
        "f": "fail",
    }
    main(test_data01)

(1)实践结果:

已经能够获取最终值!

(2)案例分析:

能够通过该方式获取到指定值,说明也能够通过其获取其路径。

(3)思考:

如何获取路径呢?每一次递归都可以获得其路径的一部分,那么就可以通过拼接的方式获取其路径。

6、采用递归方式,同时递归字典或列表:版本6(20210516修改)

def print_value_paths(data, path=""):
    if isinstance(data, dict):
        for key, value in data.items():
            print_value_paths(value, path + f'["{key}"]')
    elif isinstance(data, list):
        for index, item in enumerate(data):
            print_value_paths(item, path + f"[{index}]")
    else:
        print(f"{path} = {data}")


test_data = {
    "a":{"b":1},
    "c":[2,{"d":3}],
    "e":"success",
}

# 调用函数输出每个元素的取值路径
print_value_paths(test_data, "test_data")

# 示例字典数据
data = {
    "a": 1,
    "b": [
        0,
        1,
        2,
        {
            "a": [1, 2, 4]
        },
        [5, 6]
    ]
}

print_value_paths(data, "data")

7、将所有路径输出到列表中:版本7(20210516修改)

def get_value_paths(data, path=""):
    value_paths = []

    if isinstance(data, dict):
        for key, value in data.items():
            value_paths.extend(get_value_paths(value, path + f'["{key}"]'))
    elif isinstance(data, list):
        for index, item in enumerate(data):
            value_paths.extend(get_value_paths(item, path + f"[{index}]"))
    else:
        value_paths.append(f"{path} = {data}")

    return value_paths


# 示例字典数据
data = {
    "a": 1,
    "b": [
        0,
        1,
        2,
        {
            "a": [1, 2, 4]
        },
        [5, 6]
    ]
}

# 获取所有元素的取值路径列表
value_paths = get_value_paths(data, "data")

print(value_paths)

# # 输出所有元素的取值路径
# for value_path in value_paths:
#     print(value_path)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lion King

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值