¿Python reemplaza recursivamente el carácter en las claves del diccionario nested?

Estoy intentando crear una función genérica que reemplaza los puntos en las claves de un diccionario nested. Tengo una función no genérica que tiene 3 niveles de profundidad, pero debe haber una manera de hacer esto genérico. Cualquier ayuda es apreciada! Mi código hasta ahora:

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) 

ACTUALIZACIÓN: para responder a mi propia pregunta, hice una solución usando 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 

Sí, existe mejor manera:

 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: Es recursión, más en Wikipedia ).

Usé el código de @horejsek, pero lo adapté para aceptar diccionarios nesteds con listas y una función que reemplaza la cadena.

Tuve un problema similar que resolver: quería reemplazar las claves en la convención de minúsculas del subrayado para la convención de camellos y viceversa.

 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 

En realidad, todas las respuestas contienen un error que puede llevar a una escritura incorrecta en el resultado.

Tomaría la respuesta de @ngenain y la mejoraría un poco más abajo.

Mi solución se ocupará de los tipos derivados de dict ( OrderedDict , defaultdict , etc.) y también no solo de la list , sino de los tipos de set y tuple .

También hago una comprobación de tipo simple al principio de la función para los tipos más comunes para reducir el recuento de comparaciones (puede dar un poco de velocidad en las grandes cantidades de datos).

Funciona para Python 3. Reemplaza obj.items() con obj.iteritems() para 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 

Si entiendo bien las necesidades, la mayoría de los usuarios quieren convertir las claves para usarlas con mongoDB que no permite puntos en los nombres de las claves.

Aquí hay una solución recursiva simple que trata con listas y diccionarios nesteds.

 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 

Debe eliminar la clave original, pero no puede hacerlo en el cuerpo del bucle porque lanzará RunTimeError: el diccionario cambió de tamaño durante la iteración.

Para resolver esto, repita una copia del objeto original, pero modifique el objeto original:

 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}}} 

Si bien la respuesta de jllopezpino funciona, pero solo se limita al comienzo con el diccionario, aquí está la mía que funciona con la variable original, ya sea lista o 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('(.)([AZ][az]+)', r'\1_\2', name) return re.sub('([a-z0-9])([AZ])', 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 

Aquí hay una variante de 1 línea de la respuesta de @horejsek usando la comprensión de dictado para aquellos que prefieren:

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

Solo he probado esto en Python 2.7