Python之字典以层级形式输出

问题

现有字典:

a = dict(a0=1, b0=2, c0=3, d0=dict(a1=[1, 2, 3], b1=dict(a2=dict(a3=10, b3=[1, 2, 3])), c1=dict(a2=1)))

想将其按照不同的层级进行输出,即如下形式:

{
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }
}

如何构造函数mapping2str

分析

首先直观上的一个理解是,这里的多层嵌套,由于深度是未知的,所以是要使用递归的方式进行处理的。也就是要反复调用mapping2str

大体结构可以得到如下形式:

def mapping2str(mapping: abc.Mapping, **kwargs) -> str:
    ...
    for key, value in mapping.items():
        # 仅在value为字典时才会递归调用
        if isinstance(value, abc.Mapping):
            # 进入下一字典层级中
            value = mapping_to_str(value)
        else:
            # 对于非字典项,直接转化为字符串
            value = str(value)
        out += value
    ...

这里实际上很直观,基本上可以处理嵌套字典的不同层级。接下来就是缩进和花括号包裹的问题了。

我们观察最终输出的形式:

{
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }
}

对于这里的字典,关于缩进和花括号的形式有如下特征:

  • 左括号位于f"{key}:"之后,
  • 右括号缩进与f"{key}:"一致
  • 各个同级的子项缩进一致,且比父级更缩进一级
  • 仅当子项为字典时会绘制花括号

将这些内容补充到前面的代码中:

def mapping2str(mapping: abc.Mapping, prefix: str = "    ", lvl: int = 0) -> str:
    # prefix = "    "  为基础缩进符号
    # lvl = 0  为整数,表示缩进的层级,以最外层花括号的缩进作为0级,之后逐层增1,则各层的缩进符号可以写为 prefix * lvl
    sub_lvl = lvl + 1
    sub_prefix = prefix * sub_lvl

    sub_items = []
    for key, value in mapping.items():
        sub_item = sub_prefix + key + ": "
        # 仅在value为字典时才会递归调用
        if isinstance(value, abc.Mapping):  # 可以看做是在模拟d0中的b1
            # 进入下一字典层级中
            sub_item += "{\n"
            sub_item += mapping2str(value, prefix, sub_lvl)  # 递归调用的部分实际上可以看做是在模拟b1中的a2
            sub_item += "\n" + sub_prefix + "}"
        else:
            # 对于非字典项,直接转化为字符串
            sub_item += str(value)  # 模拟d0中的a1
        sub_items.append(sub_item)
    # 使用'\n'连接a1、b1、c1
    sub_items = "\n".join(sub_items)
    return sub_items

此时输出为:

    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }

可见缺失了头尾的花括号,可以在最后根据lvl来补上他们:

def mapping2str(mapping: abc.Mapping, prefix: str = "    ", lvl: int = 0) -> str:
    # prefix = "    "  为基础缩进符号
    # lvl = 0  为整数,表示缩进的层级,以最外层花括号的缩进作为0级,之后逐层增1,则各层的缩进符号可以写为 prefix * lvl
    sub_lvl = lvl + 1
    sub_prefix = prefix * sub_lvl

    sub_items = []
    for key, value in mapping.items():
        sub_item = sub_prefix + key + ": "
        # 仅在value为字典时才会递归调用
        if isinstance(value, abc.Mapping):  # 可以看做是在模拟d0中的b1
            # 进入下一字典层级中
            sub_item += "{\n"
            sub_item += mapping2str(value, prefix, sub_lvl)  # 递归调用的部分实际上可以看做是在模拟b1中的a2
            sub_item += "\n" + sub_prefix + "}"
        else:
            # 对于非字典项,直接转化为字符串
            sub_item += str(value)  # 模拟d0中的a1
        sub_items.append(sub_item)
    # 使用'\n'连接a1、b1、c1
    sub_items = "\n".join(sub_items)
    if lvl == 0:
        # 加上头尾的花括号
        sub_items = '{\n' + sub_items + '\n}'
    return sub_items

进一步

虽然上面的代码已经实现了想要的目的,但是总觉的最后的那个花括号有些扎眼。有没有办法把它去掉呢?
直接的想法就是在前面构造每一项的过程中,自动的包含头尾的花括号。
于是,我们尝试重构代码,得到一个新的版本:

def mapping2str_v1(mapping: abc.Mapping, prefix: str = "    ", lvl: int = 0) -> str:
    # prefix = "    "  为基础缩进符号
    # lvl = 0  为整数,表示缩进的层级,以最外层花括号的缩进作为0级,之后逐层增1,则各层的缩进符号可以写为 prefix * lvl
    sub_lvl = lvl + 1
    cur_prefix = prefix * lvl
    sub_prefix = prefix * sub_lvl

    sub_items = ["{"]  # 这一部分的缩进由上一层的sub_item的起始字符`sub_prefix + key + ": `确定
    for key, value in mapping.items():
        sub_item = sub_prefix + key + ": "
        # 仅在value为字典时才会递归调用
        if isinstance(value, abc.Mapping):  # 可以看做是在模拟d0中的b1
            # 进入下一字典层级中
            sub_item += mapping2str_v1(value, prefix, sub_lvl)  # 递归调用的部分实际上可以看做是在模拟b1中的a2
        else:
            # 对于非字典项,直接转化为字符串
            sub_item += str(value)  # 模拟d0中的a1
        sub_items.append(sub_item)
    sub_items.append(cur_prefix + "}")
    # 使用'\n'连接a1、b1、c1
    sub_items = "\n".join(sub_items)
    return sub_items

输出为:

{
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }
}

再进一步

如果我们要求,额外传入一个参数限制展开字典的最大层级,即,如果max_lvl=0则表示整个输出直接写成一行,如果max_lvl=1,则表示各个*0的孩子都写成一行,所谓展开1级。后面的层级以此类推。

可以写成如下代码:

def mapping2str_v2(mapping: abc.Mapping, *, prefix: str = "    ", lvl: int = 0, max_lvl: int = 3) -> str:
    sub_lvl = lvl + 1
    cur_prefix = prefix * lvl
    sub_prefix = prefix * sub_lvl

    if lvl == max_lvl:  # 用来处理最大层级下的各项内容,直接按照字符串一行输出
        sub_items = str(mapping)
    else:
        sub_items = ["{"]
        for k, v in mapping.items():
            sub_item = sub_prefix + k + ": "
            if isinstance(v, abc.Mapping):
                sub_item += mapping2str_v2(v, prefix=prefix, lvl=sub_lvl, max_lvl=max_lvl)
            else:
                sub_item += str(v)  # 更多是用于处理各个层级非字典项的输出
            sub_items.append(sub_item)
        sub_items.append(cur_prefix + "}")
        sub_items = "\n".join(sub_items)
    return sub_items

各个输出为:

print("max_lvl=0 -->\n", mapping2str_v2(a, max_lvl=0))
print("max_lvl=1 -->\n", mapping2str_v2(a, max_lvl=1))
print("max_lvl=2 -->\n", mapping2str_v2(a, max_lvl=2))
print("max_lvl=3 -->\n", mapping2str_v2(a, max_lvl=3))
print("max_lvl=4 -->\n", mapping2str_v2(a, max_lvl=4))
print("max_lvl=5 -->\n", mapping2str_v2(a, max_lvl=5))
max_lvl=0 -->
 {'a0': 1, 'b0': 2, 'c0': 3, 'd0': {'a1': [1, 2, 3], 'b1': {'a2': {'a3': 10, 'b3': [1, 2, 3]}}, 'c1': {'a2': 1}}}
max_lvl=1 -->
 {
    a0: 1
    b0: 2
    c0: 3
    d0: {'a1': [1, 2, 3], 'b1': {'a2': {'a3': 10, 'b3': [1, 2, 3]}}, 'c1': {'a2': 1}}
}
max_lvl=2 -->
 {
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {'a2': {'a3': 10, 'b3': [1, 2, 3]}}
        c1: {'a2': 1}
    }
}
max_lvl=3 -->
 {
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {'a3': 10, 'b3': [1, 2, 3]}
        }
        c1: {
            a2: 1
        }
    }
}
max_lvl=4 -->
 {
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }
}
max_lvl=5 -->
 {
    a0: 1
    b0: 2
    c0: 3
    d0: {
        a1: [1, 2, 3]
        b1: {
            a2: {
                a3: 10
                b3: [1, 2, 3]
            }
        }
        c1: {
            a2: 1
        }
    }
}
  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值