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?
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
.
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
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.
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
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?
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
.
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
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.
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