Python рекурсивно заменяет символ в ключах вложенного словаря?
Я пытаюсь создать универсальную функцию, которая заменяет точки в ключах вложенного словаря. У меня есть не-общая функция, которая идет на 3 уровня в глубину, но должен быть способ сделать это generic. Любая помощь приветствуется! Мой код:
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
Ответы
Ответ 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
(Edit: Рекурсия, больше на Wikipedia.)
Ответ 2
Я использовал код @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
Ответ 3
Здесь простое рекурсивное решение, которое имеет дело с вложенными списками и dictionnaries.
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
Ответ 4
На самом деле все ответы содержат ошибку, которая может привести к неправильному набору результата.
Я бы взял ответ @ngenain и немного улучшил его.
Мое решение позаботится о типах, полученных из dict
(OrderedDict
, defaultdict
и т.д.), а также о не только list
, но set
и tuple
типах.
Я также делаю простую проверку типа в начале функции для наиболее распространенных типов, чтобы уменьшить количество сравнений (может дать немного скорости в больших объемах данных).
Работает для Python 3. Замените obj.items()
на obj.iteritems()
для Py2.
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, который не позволяет использовать точки в именах ключей.
Ответ 5
Вам нужно удалить исходный ключ, но вы не можете сделать это в теле цикла, потому что он будет бросать RunTimeError: измененный размер словаря во время итерации.
Чтобы решить эту проблему, выполните итерацию через копию исходного объекта, но измените исходный объект:
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}}}
Ответ 6
В то время как ответ jllopezpino работает, но ограничивается только началом со словарем, вот мой, который работает с исходной переменной - это либо список, либо dict.
def fix_camel_cases(data):
def convert(name):
# https://stackoverflow.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
Здесь 1-лайнерский вариант ответа @horejsek, используя определение 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))