如何递归替换匹配键的字典值?

6
我正在尝试使用递归方法,取出字典中所有匹配key的键,并将其值替换为replace_value。理论上,字典可以无限嵌套,因此必须使用递归方法实现。

目前的解决方案能够正确替换值,但会引发一个异常:“调用Python对象时超过了最大递归深度”(而且这是不良使用递归方式,没有返回值)。

def replace_item(obj, key, replace_value):
    """
    Replaces the dictionary value of key with replace_value in the obj dictionary.
    """
    if key in obj:
        obj[key] = replace_value
    
    for k, v in obj.items():
        if isinstance(v, dict):
            item = replace_item(v, key, replace_value)

            if item is not None:
                item = replace_value

     return obj

一个例子是它执行的操作如下: 原始字典
person_dict = {
    "name": "Alex",
    "sex": "M",
    "title": "Engineer",
    "misc": {
        "mailbox": "3A",
        "work_type": "remote"
    }
}

然后我会调用replace_item(person_dict, "work_type", "office"),最好改为返回更新后的字典(person_dict = replace_item(person_dict, "work_type", "office"))。

替换值字典

person_dict = {
    "name": "Alex",
    "sex": "M",
    "title": "Engineer"
    "misc": {
        "mailbox": "3A",
        "work_type": "office"
    }
}

如何修复递归问题?
5个回答

11

你有一些奇怪的行为,期望有一个 return 但是没有。此外,你的描述表明它应该替换嵌套键,但是你的代码会错过顶层字典中没有键但在较低级别中有的情况。我认为下面的代码可以实现你所描述的:

def replace_item(obj, key, replace_value):
    for k, v in obj.items():
        if isinstance(v, dict):
            obj[k] = replace_item(v, key, replace_value)
    if key in obj:
        obj[key] = replace_value
    return obj

编辑:如@dashiell所建议的那样,在递归搜索/替换之后将顶级重新分配移动可避免key存在于replace_value中时出现无限递归陷阱。


1
我认为你想要的是 obj[k] = replace.... 而不是 obj[key][k] = repla.... 否则,如果 replace_value 是一个包含键 key 的字典,你将会得到一个无限循环,最终导致递归深度异常。 - Aaron
2
将替换操作放在递归下面也是一个不错的主意。例如,如果有人设置key=some_key,replace_value={some_key:some_value},可能会出现最大递归深度错误。 - dashiell

2

以下是一种函数式编程的方式:

def replace(obj, key, val):
    return {k: replace(val if k == key else v, key, val) 
        for k,v in obj.items()} if isinstance(obj, dict) else obj

在Python中效率不高(因为所有的值/子字典都被重新创建),但展示了如何在没有副作用和不改变对象的情况下解决问题。


2
这里有一个解决方案,它消除了对递归的需求(尽管可能json在将字典转换为字符串然后再转回来时会使用递归)。
import json
import re

def replace_item(obj, key_to_replace, replace_value):
    pattern = re.compile(f'"{key_to_replace}":[^,]+')

    config_str = json.dumps(obj)

    assert pattern.search(config_str)
    config_str = re.sub(pattern, f'\"{key_to_replace}\": {replace_value}', config_str)

    return json.loads(config_str)

1
def replace_item(obj, key, replace_value):
    """
    Replaces the dictionary value of key with replace_value in the obj dictionary.
    """
    if key in obj:
        obj[key] = replace_value

    for k, v in obj.items():
        if isinstance(v, dict):
            replace_item(v, key, replace_value)

我认为这已经足够了,不需要额外的变量。在大多数系统中,默认递归深度约为1000,您可以通过https://docs.python.org/3/library/sys.html#sys.setrecursionlimit进行更改。


1

当有一个字典列表的列表时,您可以使用此代码...

def replace_item(obj, key_to_replace, replace_value):
    if type(obj)==list:
        newObj=[]
        for objx in obj:
            objx=replace_item(objx, key_to_replace, replace_value)
            newObj.append(objx)
        return newObj
    obj=dict((replace_value if key==key_to_replace else key, value) for (key, value) in obj.items())
    for k in obj.keys():
        if type(obj[k]) in [dict,list]:
            obj[k]=replace_item(obj[k], key_to_replace, replace_value)
    return obj

你好,请查看 https://meta.stackoverflow.com/editing-help 谢谢! - Eric Aya
请使用代码格式化以获得更好的可读性 - benicamera

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接