如何使用递归扩展词典

时间:2017-10-10 09:24:23

标签: python recursion

为简化复杂配置,所有内容都存储在字典中。 配置的某些部分会重复多次,并且很容易复制和粘贴它们,即使它们经常更改。

为了避免这种重复,在词典中有一些键(以' _'开头)将通用参数组合在一起:即

{
    ....
    "_common_settings": {
        val1: 0,
        val2: 1,
    }
    ....
}

其他地方可以通过以下方式调用这组设置:

{
    ...
    "extra": "_common_settings"
    ...
}

技巧由extra键完成:每次找到它时,它都会被其定义所取代。它仅作为字典键出现,如果是列表元素则不进行管理。

整个配置可以是字典和列表的混合,字典中的字典或任何其他组合和任何深度。

这是初始配置的简短示例:

initial_cfg = {

    # -------------------------------------------------------------------
    # common definitions

    "_other_common_params": {
        "fields": "all",
        "polling": 5
    },

    "_extra_params100": {
        "ip": "10.1.0.1",
        "name": "db100",
        "extra": "_other_common_params"
    },

    "_extra_params200": {
        "ip": "10.1.0.2",
        "name": "db200",
        "extra": "_other_common_params"
    },

    # -------------------------------------------------------------------
    # page-specific definitions

    "id101": {
        "db": [
            {   "id": "id101",
                "table": "table101",
                "extra": "_extra_params100"
            }
        ]
    },

    "id201": {
        "db": [
            {   "id": "id201",
                "table": "table201",
                "extra": "_extra_params200"
            },

            {   "id": "id202",
                "table": "table202",
                "extra": "_extra_params200"
            }
        ]
    }
}

这是我想要获得的最终结果(以_开头的键被删除,因为它们没用):

final_cfg = {

    "id101": {
        "db": [
            {   "id": "id101",
                "table": "table101",
                "ip": "10.1.0.1",
                "name": "db100",
                "fields": "all",
                "polling": 5,
            }
        ]
    },

    "id201": {
        "db": [
            {   "id": "id201",
                "table": "table201",
                "ip": "10.1.0.2",
                "name": "db200",
                "fields": "all",
                "polling": 5
            },

            {   "id": "id202",
                "table": "table202",
                "ip": "10.1.0.2",
                "name": "db200",
                "fields": "all",
                "polling": 5             
            }
        ]
    }
}

我在使用这种递归替换方面遇到了麻烦,因为根据处理initial_config的顺序,结果会发生变化,而extra中会留下一些final_config

这是我现在使用的代码。递归不是我的面包,我无法修复它来管理initial_config的整个解码。

def init_pages_config():

    global pages_config

    def rebuild_pages_config(branch):
        if type(branch) is list:
            # in lists simple recursion, no substitution
            for b in branch:
                rebuild_pages_config(b)

        elif type(branch) is dict:
            # in dictionaries substitution, then recursion
            if "extra" in branch:
                key = branch["extra"]
                del branch["extra"]
                branch.update(pages_config[key])

            for b in branch:
                rebuild_pages_config(branch[b])

    rebuild_pages_config(pages_config)

    # remove the entries beginning with _ 
    new_dict = {}
    for pc in pages_config.keys():
        if not pc.startswith("_"):
            new_dict[pc] = pages_config[pc]
    pages_config = new_dict

    from pprint import pprint
    pprint(pages_config)

1 个答案:

答案 0 :(得分:2)

您需要首先处理您的_extra映射。这些形成一个图表,您可以使用队列来确保它们完全展开:

from collections import deque
from itertools import chain
from functools import singledispatch

def expand_config(config):
    extras = build_substitutions(config)
    return substitute_recurse(config, extras)

def build_substitutions(config):
    substitutions = {}
    queue = deque((k, o.copy()) for k, o in config.items() if k[0] == '_')
    while queue:
        key, subst = queue.pop()
        if 'extra' in subst:
            if subst['extra'] not in substitutions:
                # extra keys not yet processed
                queue.appendleft((key, subst))
                continue
            subst.update(substitutions[subst.pop('extra')])
        substitutions[key] = subst
    return substitutions

@singledispatch
def substitute_recurse(obj, extras):
    return obj

@substitute_recurse.register(dict)
def _dict(d, extras):
    extra = extras.get(d.get('extra'), {})
    return {k: substitute_recurse(v, extras) 
            for k, v in chain(d.items(), extra.items())
            if k[0] != '_' and k != 'extra'}

@substitute_recurse.register(list)
def _list(l, extras):
    return [substitute_recurse(v, extras) for v in l]

递归全部由using single dispatch处理,这使得每个类型的处理程序变得更加简单。