文章目录
-
- 问题
- 分析
- 进一步
- 再进一步
问题
现有字典:
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)))
- 1
想将其按照不同的层级进行输出,即如下形式:
{
a0: 1
b0: 2
c0: 3
d0: {
a1: [1, 2, 3]
b1: {
a2: {
a3: 10
b3: [1, 2, 3]
}
}
c1: {
a2: 1
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
如何构造函数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
...
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这里实际上很直观,基本上可以处理嵌套字典的不同层级。接下来就是缩进和花括号包裹的问题了。
我们观察最终输出的形式:
{
a0: 1
b0: 2
c0: 3
d0: {
a1: [1, 2, 3]
b1: {
a2: {
a3: 10
b3: [1, 2, 3]
}
}
c1: {
a2: 1
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
对于这里的字典,关于缩进和花括号的形式有如下特征:
- 左括号位于
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
此时输出为:
a0: 1
b0: 2
c0: 3
d0: {
a1: [1, 2, 3]
b1: {
a2: {
a3: 10
b3: [1, 2, 3]
}
}
c1: {
a2: 1
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
可见缺失了头尾的花括号,可以在最后根据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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
进一步
虽然上面的代码已经实现了想要的目的,但是总觉的最后的那个花括号有些扎眼。有没有办法把它去掉呢?
直接的想法就是在前面构造每一项的过程中,自动的包含头尾的花括号。
于是,我们尝试重构代码,得到一个新的版本:
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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
输出为:
{
a0: 1
b0: 2
c0: 3
d0: {
a1: [1, 2, 3]
b1: {
a2: {
a3: 10
b3: [1, 2, 3]
}
}
c1: {
a2: 1
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
再进一步
如果我们要求,额外传入一个参数限制展开字典的最大层级,即,如果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
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
各个输出为:
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))
- 1
- 2
- 3
- 4
- 5
- 6
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
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71