python - 将嵌套字典中出现的所有类型提升到顶级键

标签 python dictionary

我在一个项目中需要在嵌套字典中查找所有给定类型并将它们全部移动到同一字典中的顶级键。

到目前为止,我有下面的代码,它似乎有效。在示例中,我正在查找所有整数项并将它们移动到 'numbers' 键。

如果 lift_numbers_to_top 函数制作并返回字典的副本,而不是就地编辑它,我更喜欢它,但我还没有找到一种很好的方法来传递如果有意义的话,将数字从递归函数复制回自身。

a_dictionary = {
    "one": 1,
    "two": 2,
    "text": "Hello",
    "more_text": "Hi",
    "internal_dictionary": {
        "three": 3,
        "two": 2,
        "even_more_text": "Hey",
        "another_internal_dictionary": {
            "four": 4,
            "five": 5,
            "last_text": "howdy"
        }
    }
}


def extract_integers(dictionary, level_key=None):
    numbers = {}
    for key in dictionary:
        if type(dictionary[key]) == int:
            numbers[level_key + "__" + key if level_key else key] = dictionary[key]
    return numbers


def lift_numbers_to_top(dictionary, level_key=None):
    numbers = {}
    if type(dictionary) == dict:
        numbers = extract_integers(dictionary, level_key)
        for key in numbers:
            keyNumber = key.split('__')[-1]
            del dictionary[keyNumber]
        for key in dictionary:
            numbers = {**numbers, **lift_numbers_to_top(dictionary[key], key)}
    return numbers


a_dictionary['numbers'] = lift_numbers_to_top(a_dictionary)
print(a_dictionary)

结果:

{
    'text': 'Hello',
    'more_text': 'Hi',
    'internal_dictionary': {
        'even_more_text': 'Hey',
        'another_internal_dictionary': {
            'last_text': 'howdy'
        },
    },
    'numbers': {
        'one': 1,
        'two': 2,
        'internal_dictionary__two': 2,
        'internal_dictionary__three': 3,
        'another_internal_dictionary__four': 4,
        'another_internal_dictionary__five': 5,
    }
}

最佳答案

使用匹配函数来确定要提升的内容,并将键值对移动到的目标对象传递给递归调用。如果该目标缺失,您就知道当前的调用是针对顶层的。匹配函数应返回新字典的新键。

要生成一个新字典,只需生成一个新字典并将递归结果放入该对象即可。

我更喜欢使用@singledispatch()递归时处理不同类型:

from functools import singledispatch

@singledispatch
def lift_values(obj, match, targetname=None, **kwargs):
    """Lift key-value pairs from a nested structure to the top

    For key-value pairs anywhere in the nested structure, if
    match(path, value) returns a value other than `None`, the 
    key-value pair is moved to the top-level dictionary when targetname
    is None, or to a new dictionary stored under targetname is not None,
    using the return value of the match function as the key. path
    is the tuple of all keys and indices leading to the value.

    For example, for an input 

        {'foo': True, 'bar': [{'spam': False, 'ham': 42}]}

    and the match function lambda p, v: p if isinstance(v, bool) else None
    and targetname "flags", this function returns

        {'flags': {('foo',): True, ('bar', 0, 'spam'): False}, 'bar': [{'ham': 42}]}

    """
    # leaf nodes, no match testing needed, no moving of values
    return obj

@lift_values.register(list)
def _handle_list(obj, match, _path=(), **kwargs):
    # list values, no lifting, just passing on the recursive call
    return [lift_values(v, match, _path=_path + (i,), **kwargs)
            for i, v in enumerate(obj)]

@lift_values.register(dict)
def _handle_list(obj, match, targetname=None, _path=(), _target=None):
    result = {}
    if _target is None:
        # this is the top-level object, key-value pairs are lifted to
        # a new dictionary stored at this level:
        if targetname is not None:
            _target = result[targetname] = {}
        else:
            # no target name? Lift key-value pairs into the top-level
            # object rather than a separate sub-object.
            _target = result

    for key, value in obj.items():
        new_path = _path + (key,)
        new_key = match(new_path, value)
        if new_key is not None:
            _target[new_key] = value
        else:
            result[key] = lift_values(
                value, match, _path=new_path, _target=_target)

    return result

我添加了列表的调度函数;您的示例不使用列表,但这些在 JSON 数据结构中很常见,因此我预计您可能仍然需要它。

匹配函数必须接受两个参数,即找到此键值对的对象的路径以及值。它应该返回一个要使用的新 key ,如果不提升该值,则返回“None”。

对于您的情况,匹配函数将是:

def lift_integers(path, value):
    if isinstance(value, int):
        return '__'.join(path[-2:])

result = lift_values(a_dictionary, lift_integers, 'numbers')

示例输入字典的演示:

>>> from pprint import pprint
>>> def lift_integers(path, value):
...     if isinstance(value, int):
...         return '__'.join(path[-2:])
...
>>> lift_values(a_dictionary, lift_integers, 'numbers')
{'numbers': {'one': 1, 'two': 2, 'internal_dictionary__three': 3, 'internal_dictionary__two': 2, 'another_internal_dictionary__four': 4, 'another_internal_dictionary__five': 5}, 'text': 'Hello', 'more_text': 'Hi', 'internal_dictionary': {'even_more_text': 'Hey', 'another_internal_dictionary': {'last_text': 'howdy'}}}
>>> pprint(_)
{'internal_dictionary': {'another_internal_dictionary': {'last_text': 'howdy'},
                         'even_more_text': 'Hey'},
 'more_text': 'Hi',
 'numbers': {'another_internal_dictionary__five': 5,
             'another_internal_dictionary__four': 4,
             'internal_dictionary__three': 3,
             'internal_dictionary__two': 2,
             'one': 1,
             'two': 2},
 'text': 'Hello'}

就我个人而言,我会使用完整路径作为提升字典中的键以避免名称冲突;通过将完整的 path 连接到具有某些唯一分隔符的新字符串键中,或者仅将 path 元组本身设置为新键:

>>> lift_values(a_dictionary, lambda p, v: p if isinstance(v, int) else None, 'numbers')
{'numbers': {('one',): 1, ('two',): 2, ('internal_dictionary', 'three'): 3, ('internal_dictionary', 'two'): 2, ('internal_dictionary', 'another_internal_dictionary', 'four'): 4, ('internal_dictionary', 'another_internal_dictionary', 'five'): 5}, 'text': 'Hello', 'more_text': 'Hi', 'internal_dictionary': {'even_more_text': 'Hey', 'another_internal_dictionary': {'last_text': 'howdy'}}}
>>> pprint(_)
{'internal_dictionary': {'another_internal_dictionary': {'last_text': 'howdy'},
                         'even_more_text': 'Hey'},
 'more_text': 'Hi',
 'numbers': {('internal_dictionary', 'another_internal_dictionary', 'five'): 5,
             ('internal_dictionary', 'another_internal_dictionary', 'four'): 4,
             ('internal_dictionary', 'three'): 3,
             ('internal_dictionary', 'two'): 2,
             ('one',): 1,
             ('two',): 2},
 'text': 'Hello'}

关于python - 将嵌套字典中出现的所有类型提升到顶级键,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51584385/

相关文章:

python - 列表列表未正确分配值Python

python - 元组与字符串与卡住集。不可变对象(immutable对象)和内存中的副本数量

python - 使用 webapp2 session 的 Google Cloud Endpoints 身份验证

java - 使用 Gson 解析 JSON 映射/字典?

swift - 为什么swift2.0的字典顺序是这样的?

Java 8将集合转换为一键多值映射的方法

python - 在Python中重命名文件: No such file or directory

python - 从 scipy.interpolate 导入 RegularGridInterpolator 时出错

python - 如何获得一套字典?

c++ - std::map::erase 无限循环