提问者:小点点

python-使用映射器字典转换嵌套字典值


我正在寻找一个优雅的蟒蛇解决方案

我有两个字典,一个输入字典和一个包含映射信息的字典——见下文。我的目标是将输入字典中的任何值转换为相应的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

共3个答案

匿名用户

代码:

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}}

匿名用户

我已将代码重塑为一种方法,该方法接受两种输入:InputDictmapperDict。它使用递归来处理类型dict,因为它的代码只是映射dicts,所以如果它是dictdict,只需调用它自己。

我知道您刚刚使用了两级映射,但明天当您有更多嵌套时,您不希望您的代码因为使用了两级嵌套而中断。相反,您的代码应该扩展以处理任何类型的嵌套及其类型。(遵循意识形态,写一次,用很多次。)

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)

此代码的优点:

  • 可伸缩的代码,所以今天你有2个嵌套,但即使你有100个,也没关系。代码可以扩展

性能:

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是否提供该路径。如果是这样,那么您可以简单地将该值重新分配给displaydictionary中的相应键。

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