Python递归替换嵌套字典键中的字符?

Posted

技术标签:

【中文标题】Python递归替换嵌套字典键中的字符?【英文标题】:Python recursively replace character in keys of nested dictionary? 【发布时间】:2012-07-26 21:26:43 【问题描述】:

我正在尝试创建一个通用函数来替换嵌套字典键中的点。我有一个非泛型函数,深度为 3 级,但必须有一种方法可以做到这一点。任何帮助表示赞赏!到目前为止我的代码:

output = 'key1': 'key2': 'value2', 'key3': 'key4 with a .': 'value4', 'key5 with a .': 'value5' 

def print_dict(d):
    new = 
    for key,value in d.items():
        new[key.replace(".", "-")] = 
        if isinstance(value, dict):
            for key2, value2 in value.items():
                new[key][key2] = 
                if isinstance(value2, dict):
                    for key3, value3 in value2.items():
                        new[key][key2][key3.replace(".", "-")] = value3
                else:
                    new[key][key2.replace(".", "-")] = value2
        else:
            new[key] = value
    return new

print print_dict(output)

更新:为了回答我自己的问题,我使用 json object_hooks 做了一个解决方案:

import json

def remove_dots(obj):
    for key in obj.keys():
        new_key = key.replace(".","-")
        if new_key != key:
            obj[new_key] = obj[key]
            del obj[key]
    return obj

output = 'key1': 'key2': 'value2', 'key3': 'key4 with a .': 'value4', 'key5 with a .': 'value5'
new_json = json.loads(json.dumps(output), object_hook=remove_dots) 

print new_json

【问题讨论】:

回答你自己的问题,你回答你自己的问题,而不是编辑它。 使用我的解决方案,因为我的解决方案快十倍。 这样做的好方法。 object_hook 确实简化了整个事情,特别是在我使用名为“include”的“键”的情况下,它需要递归加载额外的 JSON 文件以形成单个多维字典。 由于某种莫名其妙的原因,这个使用上述 remove_dots() 的 object_hook 方法只替换了 一些 键名。我有一些保留点。这可能与 obj.keys() 函数中的一些奇怪的排序问题有关吗?我需要制作一个有序的字典吗?我以为 Python3 没有 dict 排序问题? 【参考方案1】:

是的,还有更好的方法:

def print_dict(d):
    new = 
    for k, v in d.iteritems():
        if isinstance(v, dict):
            v = print_dict(v)
        new[k.replace('.', '-')] = v
    return new

(编辑:它是递归,更多关于Wikipedia。)

【讨论】:

-1 因为它不替换初始键,它使用替换的字符添加一个新键 @bk0 它创建新字典。初始键不在返回的新字典中。 此解决方案仅在所有值都是 dicts 时才有效。如果一个值是一个字典列表,它会失败 - 列表中的字典将无法到达。 @aryeh 是的,这也是个问题。不能为所有事情写一些通用的解决方案。 :-)【参考方案2】:

实际上所有的答案都包含一个错误,可能导致结果输入错误。

我会接受@ngenain 的回答并在下面稍微改进一下。

我的解决方案将关注从dictOrderedDictdefaultdict 等)派生的类型,并且不仅关注list,还关注settuple 类型。

我还在函数开头对最常见的类型进行了简单的类型检查,以减少比较次数(在大量数据中可能会加快速度)。

适用于 Python 3。对于 Py2,将 obj.items() 替换为 obj.iteritems()

def change_keys(obj, convert):
    """
    Recursively goes through the dictionary obj and replaces keys with the convert function.
    """
    if isinstance(obj, (str, int, float)):
        return obj
    if isinstance(obj, dict):
        new = obj.__class__()
        for k, v in obj.items():
            new[convert(k)] = change_keys(v, convert)
    elif isinstance(obj, (list, set, tuple)):
        new = obj.__class__(change_keys(v, convert) for v in obj)
    else:
        return obj
    return new

如果我理解需求正确,大多数用户都希望转换键以将它们与不允许键名中的点的 mongoDB 一起使用。

【讨论】:

这个是最好的。它同时支持 python2 和 python3,但不需要“if isinstance(obj, (str, int, float))”部分。没有这条线也可以。 不错的答案。为了完整起见,我将为您的答案添加转换功能:def convert(k): return k.replace('.', '-') @F.Tamy ... 在处理大型词典时可以节省时间。【参考方案3】:

我使用了@horejsek 的代码,但我对其进行了调整以接受带有列表的嵌套字典和一个替换字符串的函数。

我有一个类似的问题要解决:我想将下划线小写约定中的键替换为骆驼大小写约定,反之亦然。

def change_dict_naming_convention(d, convert_function):
    """
    Convert a nested dictionary from one convention to another.
    Args:
        d (dict): dictionary (nested or not) to be converted.
        convert_function (func): function that takes the string in one convention and returns it in the other one.
    Returns:
        Dictionary with the new keys.
    """
    new = 
    for k, v in d.iteritems():
        new_v = v
        if isinstance(v, dict):
            new_v = change_dict_naming_convention(v, convert_function)
        elif isinstance(v, list):
            new_v = list()
            for x in v:
                new_v.append(change_dict_naming_convention(x, convert_function))
        new[convert_function(k)] = new_v
    return new

【讨论】:

它可以工作,除非 d 不是一个字典,所以你不能调用 d.items()。我的 dict 包含一个字符串数组,它在递归时会失败。在函数的根中检查 isinstance(d, dict),如果为 false,则返回 d。那么它应该适用于任何事情。【参考方案4】:

这是一个处理嵌套列表和字典的简单递归解决方案。

def change_keys(obj, convert):
    """
    Recursivly goes through the dictionnary obj and replaces keys with the convert function.
    """
    if isinstance(obj, dict):
        new = 
        for k, v in obj.iteritems():
            new[convert(k)] = change_keys(v, convert)
    elif isinstance(obj, list):
        new = []
        for v in obj:
            new.append(change_keys(v, convert))
    else:
        return obj
    return new

【讨论】:

好点,但它强制类型从dict派生类转换回dict。例如,您可能会丢失OrderedDict 的密钥顺序。我已经发布了基于您的改进的答案。 对于 python3 使用 obj.items(): 代替。【参考方案5】:

你必须删除原始键,但你不能在循环体中这样做,因为它会抛出 RunTimeError: dictionary changed size during iteration。

为了解决这个问题,迭代原始对象的副本,但修改原始对象:

def change_keys(obj):
    new_obj = obj
    for k in new_obj:
            if hasattr(obj[k], '__getitem__'):
                    change_keys(obj[k])
            if '.' in k:
                    obj[k.replace('.', '$')] = obj[k]
                    del obj[k]

>>> foo = 'foo': 'bar': 'baz.121': 1
>>> change_keys(foo)
>>> foo
'foo': 'bar': 'baz$121': 1

【讨论】:

它在if hasattr(obj[k], '__getitem__'): 行中给出了以下错误TypeError: string indices must be integers 而不是hasattr(...) 尝试:from collection import Mapping 然后if isinstance(obj[k], Mapping)...。此更改具有相同的目标(尝试确定该值是否为 [嵌套] 字典),但应该更稳定。【参考方案6】:

虽然 jllopezpino 的答案有效,但仅限于以字典开头,这是我的,原始变量是列表或字典。

def fix_camel_cases(data):
    def convert(name):
        # https://***.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case
        s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
        return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()

    if isinstance(data, dict):
        new_dict = 
        for key, value in data.items():
            value = fix_camel_cases(value)
            snake_key = convert(key)
            new_dict[snake_key] = value
        return new_dict

    if isinstance(data, list):
        new_list = []
        for value in data:
            new_list.append(fix_camel_cases(value))
        return new_list

    return data

【讨论】:

【参考方案7】:

这是@horejsek 的答案的 1-liner 变体,对喜欢的人使用 dict 理解:

def print_dict(d):
    return k.replace('.', '-'): print_dict(v) for k, v in d.items() if isinstance(d, dict) else d

我只在 Python 2.7 中测试过这个

【讨论】:

【参考方案8】:

您可以将所有内容转储到 JSON 替换整个字符串并重新加载 JSON

def nested_replace(data, old, new):
    json_string = json.dumps(data)
    replaced = json_string.replace(old, new)
    fixed_json = json.loads(replaced)
    return fixed_json

或者使用单线

def short_replace(data, old, new):
    return json.loads(json.dumps(data).replace(old, new))

【讨论】:

这将替换值和键中出现的字符串。原始答案要求提供密钥解决方案。如果将替换切换到 RegEx 方法,则可能仅适用于键。但是,这是一种蛮力方法,内存效率不是很高。 @ingyhere 在我的例子中,我将 XML 转换为 json 并尝试去除 @ 符号,并且不用担心影响值。对我来说,这个解决方案简洁而充分。【参考方案9】:

我猜你和我有同样的问题,将字典插入 MongoDB 集合,在尝试插入包含点 (.) 键的字典时遇到异常。

此解决方案与此处的大多数其他答案基本相同,但它更紧​​凑,并且可能不太可读,因为它使用单个语句并递归调用自身。对于 Python 3。

def replace_keys(my_dict):
    return  k.replace('.', '(dot)'): replace_keys(v) if type(v) == dict else v for k, v in my_dict.items() 

【讨论】:

确实很难阅读。我不会使用它。

以上是关于Python递归替换嵌套字典键中的字符?的主要内容,如果未能解决你的问题,请参考以下文章

嵌套python字典中的字符串替换/格式化占位符值

在字典中递归查找键

python3循环遍历嵌套字典替换指定值

用字符串替换特性键中的值

如何从 2 个 pkl 文件创建 Python 嵌套字典/将 2 个嵌套字典合并为一个?

递归遍历带有列表的嵌套字典,并替换匹配的值