How to recursively replace dictionary values with a matching key?

Question:

I’m trying to take a dictionary and find all of the keys that match key and replace their value with replace_value. The dictionaries can in theory be infinitely deep, so it must be done recursively.

My current solution replaces values correctly, but raises an exception saying "maximum recursion depth exceeded while calling a Python object" (not to mention the fact that it’s poor use of recursion with no return values).

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

An example of an operation it would perform would be the following:

Original Dictionary

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

Then I’d make a call to replace_item(person_dict, "work_type", "office"), which I’d preferably like to change over to returning the updated dictionary (person_dict = replace_item(person_dict, "work_type", "office")).

Replaced Value Dictionary

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

How can I go about fixing my recursion?

Asked By: swiftsly

||

Answers:

You have some strange behavior where you are expecting a return but you don’t have one. Also your description implies it should replace nested keys, but your code you will miss when a dictionary at the top level does not have the key but at a lower level does. I believe the below code accomplishes what you described:

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

EDIT: As @dashiell suggested, moving the top level reassignment after the recursive search/replace avoids the infinite recursion trap of having key exist in the replace_value.

Answered By: Farmer Joe
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)

I think this is enough.. no need of extra variables,
and in most of the systems, default recursion depth is around 1000, you can change it https://docs.python.org/3/library/sys.html#sys.setrecursionlimit

Answered By: Jomin

Here’s a functional-style take:

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

It’s not efficient in Python (since all values/subdicts are re-created), but demonstrates how to solve your problem without side-effects and without mutating objects.

Answered By: randomir

This you can use when there is list of dict of list…

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
Answered By: ayan das
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.