我正在寻找一个优雅的蟒蛇解决方案
我有两个字典,一个输入字典和一个包含映射信息的字典——见下文。我的目标是将输入字典中的任何值转换为相应的mapper
值,如果它们具有相同的“关键路径”,这意味着input_dico[k1][k2]
被更改为值mapper_dico[k1][k2]
不是mapper_dico[k3][k2]
mapper = {
'data_labels': {
'position': {
'outside_end': 'XL_LABEL_POSITION.OUTSIDE_END',
'inside_end': 'XL_LABEL_POSITION.INSIDE_END'
}
},
'category_axis': {
'major_tick_mark': {
'None': 'XL_TICK_MARK.NONE',
},
'tick_label_position': {
'None': 'XL_TICK_LABEL_POSITION.NONE',
'high': 'XL_TICK_LABEL_POSITION.HIGH',
'low': 'XL_TICK_LABEL_POSITION.LOW',
},
},
'chart': {
'clustered_column': 'XL_CHART_TYPE.COLUMN_CLUSTERED',
'clustered_bar': 'XL_CHART_TYPE.BAR_CLUSTERED',
'stacked_bar': 'XL_CHART_TYPE.BAR_STACKED',
},
}
输入dico示例:
{'category_axis': {'format': {'line': {'fill': 'A5300F'}},
'has_major_gridlines': False,
'major_tick_mark': 'None',
'tick_label_position': 'None',
'visible': True},
'chart': 'clustered_column',
'data_labels': {'font': {'size': 9},
'number_format': '#0.0%',
'position': 'outside_end'},
'value_axis': {'has_major_gridlines': False, 'visible': False}}
因此,您可以在这里看到,示例input_dico['data_labels']['position']:'outside_end'
,input_dico['category_axis']['tick_label_position']:'None'
和input_dico['chart']:'clustered_column'
都必须进行转换!
我现在是这样做的,这真的很笨重,很难理解,所以我正在寻找一些最有可能的递归,简短和简单的东西。如果有人有更好的解决方案,我也愿意放弃使用字典到映射值的方法!
def _reformat(display): #display is the input_dico
"""Change certain values to pptx-python classes."""
for k1, v1 in mapper.items():
if k1 in display:
if isinstance(v1, str):
display[k1] = mapper[k1]
else:
for k2, v2 in v1.items():
if k2 in display[k1]:
for k3 in v2:
if k3 == display[k1][k2]:
display[k1][k2] = mapper[k1][k2][k3]
return display
代码:
from typing import Union, Hashable, Any
def _reformat(parent: Union[dict, Hashable], mapping: dict) -> Union[dict, Any]:
"""Recursive function that transforms only these dictionary values which keys correspond to a `mapping` dict."""
if isinstance(parent, dict):
node = {}
for key, value in parent.items():
try:
node[key] = _reformat(value, mapping[key])
except KeyError:
node[key] = value
return node
else:
return mapping[parent]
示例:
mapper = {'data_labels': {'position': {'outside_end': 'XL_LABEL_POSITION.OUTSIDE_END',
'inside_end': 'XL_LABEL_POSITION.INSIDE_END'}},
'category_axis': {'major_tick_mark': {'None': 'XL_TICK_MARK.NONE'},
'tick_label_position': {'None': 'XL_TICK_LABEL_POSITION.NONE',
'high': 'XL_TICK_LABEL_POSITION.HIGH',
'low': 'XL_TICK_LABEL_POSITION.LOW'}},
'chart': {'clustered_column': 'XL_CHART_TYPE.COLUMN_CLUSTERED',
'clustered_bar': 'XL_CHART_TYPE.BAR_CLUSTERED',
'stacked_bar': 'XL_CHART_TYPE.BAR_STACKED'}}
input_dict = {'category_axis': {'format': {'line': {'fill': 'A5300F'}},
'has_major_gridlines': False,
'major_tick_mark': 'None',
'tick_label_position': 'None',
'visible': True},
'chart': 'clustered_column',
'data_labels': {'font': {'size': 9},
'number_format': '#0.0%',
'position': 'outside_end'},
'value_axis': {'has_major_gridlines': False, 'visible': False}}
converted_dict = _reformat(input_dict, mapper)
import pprint
pprint.pprint(converted_dict)
输出:
{'category_axis': {'format': {'line': {'fill': 'A5300F'}},
'has_major_gridlines': False,
'major_tick_mark': 'XL_TICK_MARK.NONE',
'tick_label_position': 'XL_TICK_LABEL_POSITION.NONE',
'visible': True},
'chart': 'XL_CHART_TYPE.COLUMN_CLUSTERED',
'data_labels': {'font': {'size': 9},
'number_format': '#0.0%',
'position': 'XL_LABEL_POSITION.OUTSIDE_END'},
'value_axis': {'has_major_gridlines': False, 'visible': False}}
我已将代码重塑为一种方法,该方法接受两种输入:InputDict
和mapperDict
。它使用递归来处理类型dict
,因为它的代码只是映射dicts
,所以如果它是dict
的dict
,只需调用它自己。
我知道您刚刚使用了两级映射,但明天当您有更多嵌套时,您不希望您的代码因为使用了两级嵌套而中断。相反,您的代码应该扩展以处理任何类型的嵌套及其类型。(遵循意识形态,写一次,用很多次。)
def _map_content(base, to):
"""Recursively maps the values from a mapping dictionary to an input
dictionary if they have the same key path.
base: input dictionary
to: mapping dictionary
"""
if not isinstance(base, dict):
return to[base]
for key in base:
if key not in to:
continue
elif isinstance(to[key], (int, str)):
base[key] = to[key]
continue
elif not isinstance(to[key], dict):
raise TypeError("I just found some data type that's not a string, int or dict. You might want to handle "
"this. Unless you meant for this. Below is what I found: \n" + to[key])
base[key] = _map_content(base[key], to[key])
return base
使用方法:
updated_input_dict = _map_content(input_dico, mapper)
print(updated_input_dict)
此代码的优点:
性能:
print('Mine: ', timeit.timeit(stmt=stAditya, number=1000000)) # 5.524377981713729
print('Jatimir:', timeit.timeit(stmt=stTwo, number=1000000)) # 6.971933613624651
print('Kasramvd:', timeit.timeit(stmt=oth, number=1000000)) # 15.090121147017014
我希望这有帮助,如果你需要什么,请使用评论部分。
您可以使用递归生成器获取所有显示
的键,然后使用reduce()
函数检查mapper
是否提供该路径。如果是这样,那么您可以简单地将该值重新分配给display
dictionary中的相应键。
from functools import reduce
def get_paths(dis, pre=[]):
for key, value in dis.items():
if isinstance(value, dict):
yield from get_paths(value, pre=pre + [key])
else:
yield pre + [key]
def _reformat(dis, pre=[]):
for keys in get_paths(dis):
try:
map_val = reduce(dict.__getitem__, keys, mapper)
except KeyError:
pass
else:
val = reduce(dict.__getitem__, keys[:-1], dis)
val[keys[-1]] = map_val
return dis