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

python-使用映射器字典转换嵌套字典值,python,python-3.x,oop,dictionary,nested,Python,Python 3.x,Oop,Dictionary,Nested,我正在寻找一个优雅的蟒蛇解决方案 我有两个字典,一个是输入字典,另一个是包含映射信息的字典——见下文。我的目标是将输入字典中的任何值转换为相应的映射器值,前提是它们具有相同的“关键路径”,这意味着输入dico[k1][k2]更改为值映射器dico[k1][k2],而不是映射器dico[k3][k2] 输入dico示例: {'category_axis': {'format': {'line': {'fill': 'A5300F'}}, 'has_major_

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

我有两个字典,一个是输入字典,另一个是包含映射信息的字典——见下文。我的目标是将输入字典中的任何值转换为相应的映射器值,前提是它们具有相同的“关键路径”,这意味着输入dico[k1][k2]更改为值映射器dico[k1][k2],而不是映射器dico[k3][k2]

输入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}}
在这里您可以看到,示例输入dico['data\u labels']['position']:'outside\u end',输入dico['category\u axis']['tick\u label\u position']:'None'和输入dico['chart']:'clustered\u column'都必须进行转换

我现在是这样做的,这真的很笨重,很难理解,所以我正在寻找一些最有可能的递归,简短和简单的东西。如果有人有更好的解决方案,我也愿意放弃使用字典到映射值的方法


您可以使用递归生成器获取所有显示键,然后使用reduce函数检查mapper是否提供该路径。如果有,那么您可以简单地将该值重新分配给显示字典中的相应键

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

您可以使用递归生成器获取所有显示键,然后使用reduce函数检查mapper是否提供该路径。如果有,那么您可以简单地将该值重新分配给显示字典中的相应键

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

我已经将代码重塑为一个方法,该方法接受两个输入:InputDict和mapperDict。它使用递归来处理dict类型,因为它的代码只是映射dict,所以如果它是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
用法:

此代码的优点:

可伸缩的代码,所以今天你有2个嵌套,但即使你有100个,也没关系。代码可以扩展。 适用于任意两个dict,这意味着您也可以将其用于不同的映射 遵循PEP8指南。 使用递归避免代码重复。 如果未处理该类型,则引发错误。 避免不必要的压痕 性能:


我希望这对您有所帮助,如果您需要什么,请使用注释部分。

我已将代码重新设计为一种方法,该方法接受两种输入:InputDict和mapperDict。它使用递归来处理dict类型,因为它的代码只是映射dict,所以如果它是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
用法:

此代码的优点:

可伸缩的代码,所以今天你有2个嵌套,但即使你有100个,也没关系。代码可以扩展。 适用于任意两个dict,这意味着您也可以将其用于不同的映射 遵循PEP8指南。 使用递归避免代码重复。 如果未处理该类型,则引发错误。 避免不必要的压痕 性能:


我希望这有帮助,如果您需要任何东西,请使用注释部分。

代码不能紧凑的一个原因是,映射器中的所有元素没有相同的嵌套级别。通过简单地在图表字典中添加一个键类型,代码几乎就变成了straigthforward。以下是修改后的结构和相应的代码:

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': {
        'type': {
            'clustered_column': 'XL_CHART_TYPE.COLUMN_CLUSTERED',
            'clustered_bar': 'XL_CHART_TYPE.BAR_CLUSTERED',
            'stacked_bar': 'XL_CHART_TYPE.BAR_STACKED',
        },
    },
}

display = {
    'category_axis': {
        'format': {'line': {'fill': 'A5300F'}},
        'has_major_gridlines': False,
        'major_tick_mark': 'None',
        'tick_label_position': 'None',
        'visible': True},
    'chart': {
        'type': 'clustered_column'},
    'data_labels': {
        'font': {'size': 9},
        'number_format': '#0.0%',
        'position': 'outside_end'},
    'value_axis': {
        'has_major_gridlines': False,
        'visible': False}
}

def reformat(display): #display is the input_dico
    for key1, dic in display.items():
        if key1 not in mapper: continue
        for key2 in dic:
            if key2 not in mapper[key1]: continue
            display[key1][key2] = mapper[key1][key2][dic[key2]]
    return display

print(reformat(display))
输出:

{'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': {
    'type': '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}
}

注意:我已将常量替换为字符串,以获得可复制粘贴的自包含代码

代码无法压缩的一个原因是映射器中的所有元素没有相同的嵌套级别。通过简单地在图表字典中添加一个键类型,代码几乎就变成了straigthforward。以下是修改后的结构和相应的代码:

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': {
        'type': {
            'clustered_column': 'XL_CHART_TYPE.COLUMN_CLUSTERED',
            'clustered_bar': 'XL_CHART_TYPE.BAR_CLUSTERED',
            'stacked_bar': 'XL_CHART_TYPE.BAR_STACKED',
        },
    },
}

display = {
    'category_axis': {
        'format': {'line': {'fill': 'A5300F'}},
        'has_major_gridlines': False,
        'major_tick_mark': 'None',
        'tick_label_position': 'None',
        'visible': True},
    'chart': {
        'type': 'clustered_column'},
    'data_labels': {
        'font': {'size': 9},
        'number_format': '#0.0%',
        'position': 'outside_end'},
    'value_axis': {
        'has_major_gridlines': False,
        'visible': False}
}

def reformat(display): #display is the input_dico
    for key1, dic in display.items():
        if key1 not in mapper: continue
        for key2 in dic:
            if key2 not in mapper[key1]: continue
            display[key1][key2] = mapper[key1][key2][dic[key2]]
    return display

print(reformat(display))
输出:

{'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': {
    'type': '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}
}
注意:我已将常量替换为字符串,以获得可复制粘贴的自包含代码

代码:

例如:

输出:

{'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': {
    'type': '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}
}
代码:

例如:

输出:

{'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': {
    'type': '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}
}

修复该示例:input_dico['data_labels']['position']['outside_end']。input_dico['data_labels']['position']是字符串而不是字典。与您提到的其他两个条目的情况相同。我还会将所有常量(如XL_LABEL_POSITION.OUTSIDE_END)更改为字符串,以便我们更容易复制粘贴代码并进行尝试。@Jatimir我突出显示了需要转换的字符串以及到达字符串所需遵循的关键路径。我明白了,但如果使用[]执行此操作,则会产生误导,它看起来像是另一个键,而不是值。所以为了弄清楚,你有两个dict,input和mapper。你想要一个

ny键包括子键如果它们存在于mapper dict中,则必须将它们的值替换为mapper dict中相同访问路径的值。正确吗?@Ludo在你真正选择一个之前,先看看我的答案中的性能统计数据。例如:input_dico['data_labels']['position']['outside_end']。input_dico['data_labels']['position']是字符串而不是字典。与您提到的其他两个条目的情况相同。我还会将所有常量(如XL_LABEL_POSITION.OUTSIDE_END)更改为字符串,以便我们更容易复制粘贴代码并进行尝试。@Jatimir我突出显示了需要转换的字符串以及到达字符串所需遵循的关键路径。我明白了,但如果使用[]执行此操作,则会产生误导,它看起来像是另一个键,而不是值。所以为了弄清楚,你有两个dict,input和mapper。如果希望任何键(包括子键)存在于mapper dict中,则必须将其值替换为mapper dict中相同访问路径的值。正确吗?@Ludo在你真正挑选一个只适用于2级嵌套的函数之前,先看看我的答案中的性能统计数据,因为我只能看到2个for循环,并且reformat函数不是递归的,对吗?@Jatimir:你是对的,但由于结构是用于格式化的,我不相信你需要超过两个层次的筑巢。一个完整的递归解决方案在这里似乎有点过头了。@scirocorics这个例子是我将要处理的字典的简化版本。大多数会有不同的嵌套级别,从1到5…@Ludo:好的,但是你的OP明确提到了两个嵌套级别,因为你提到了一个由两个键组成的键路径,所以我将我的答案改编为这个特定的情况。如果您需要处理任意嵌套,我认为最优雅的解决方案是Jatimir提出的解决方案。这只适用于两个级别的嵌套,因为我只能看到两个for循环,并且reformat函数不是递归的,对吗?@Jatimir:您是对的,但由于结构是用于格式化的,我不相信你需要超过两个层次的筑巢。一个完整的递归解决方案在这里似乎有点过头了。@scirocorics这个例子是我将要处理的字典的简化版本。大多数会有不同的嵌套级别,从1到5…@Ludo:好的,但是你的OP明确提到了两个嵌套级别,因为你提到了一个由两个键组成的键路径,所以我将我的答案改编为这个特定的情况。如果您需要处理任意嵌套,我认为最优雅的解决方案是Jatimir提出的解决方案。这似乎是最优雅的解决方案,但有一个我似乎无法解决的错误。如果将类别轴更改为:'类别轴':{'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','has_major_gridlines':'ERROR'}您将看到只有字符串错误的第一个值在那里,我可以将单个值作为长度为1的列表,然后它就可以工作了,即“has_major_gridlines”:[“error”]老实说,我不会称之为bug。您使用“ERROR”作为值的方式与示例不一致。它应该是一个字典,将has_major_网格线的可能值映射到除dict之外的其他对象,例如:“has_major_网格线”:{ERROR':'ERROR message','WARNING':'WARNING message'}:这似乎是最优雅的解决方案,但有一个我似乎无法解决的错误。如果您将category_axis更改为:“'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','has_major_gridlines':'ERROR'}您将看到只有字符串错误的第一个值在那里,我可以将单个值作为长度为1的列表,然后它就可以工作了,即“has_major_gridlines”:[“error”]老实说,我不会称之为bug。您使用“ERROR”作为值的方式与示例不一致。它应该是一个字典,将has_major_网格线的可能值映射到除dict之外的其他对象,例如:“has_major_网格线”:{ERROR':'ERROR message','WARNING':'WARNING message'}:谢谢您的回答!因为您对其进行了重新调整,它不再工作。它将所有可能的选项映射到输入dict,而不是单个选项。请看一看,我无法修复它。@Ludo您可以添加预期的示例结果吗?很快就会看到问题。jatimir回答中的输出是
理想的result@Ludo我更新了我的答案,这是最愚蠢的事情。现在它给出了同样的结果,而且速度更快:谢谢你的回答!由于您对其进行了重塑,它不再工作。它将所有可能的选项映射到输入dict,而不是单个选项。你能看一下吗?我无法修复它。@Ludo您可以添加预期的示例结果吗?很快就会发现问题所在。jatimir的答案中的输出是所需的result@Ludo我更新了我的答案,这是最愚蠢的事情。现在,它给出了相同的结果,而且速度更快:
{'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}}