resueltos recorrer lista elementos ejercicios ejemplo diccionarios diccionario dentro convertir agregar python python-2.7 dictionary recursion

python - recorrer - Acceso eficiente a diccionarios arbitrariamente profundos.



lista dentro de un diccionario python (7)

Actualicé la respuesta de Cómo usar un punto "." para acceder a los miembros del diccionario? para usar una conversión inicial que luego funcionará para los diccionarios anidados:

Puede usar la siguiente clase para permitir la indexación de puntos de los diccionarios:

class dotdict(dict): """dot.notation access to dictionary attributes""" __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__

Sin embargo, esto solo es compatible con el anidamiento si todos los diccionarios anidados también son de tipo dotdict . Ahí es donde entra la siguiente función auxiliar:

def dct_to_dotdct(d): if isinstance(d, dict): d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()}) return d

Esta función debe ejecutarse una vez en su diccionario anidado, y el resultado puede ser indexado utilizando la indexación de puntos.

Aquí hay unos ejemplos:

In [13]: mydict Out[13]: {''first'': {''second'': {''third'': {''fourth'': ''the end''}}}} In [14]: mydict = dct_to_dotdct(mydict) In [15]: mydict.first.second Out[15]: {''third'': {''fourth'': ''the end''}} In [16]: mydict.first.second.third.fourth Out[16]: ''the end''

Una nota sobre el rendimiento: esta respuesta es lenta en comparación con el acceso al diccionario estándar, solo quería presentar una opción que realmente utilizara el "acceso de punto" a un diccionario.

Supongamos que tengo un diccionario de varios niveles como este

mydict = { ''first'': { ''second'': { ''third'': { ''fourth'': ''the end'' } } } }

Me gustaría acceder así

test = get_entry(mydict, ''first.second.third.fourth'')

Lo que tengo hasta ahora es

def get_entry(dict, keyspec): keys = keyspec.split(''.'') result = dict[keys[0]] for key in keys[1:]: result = dict[key] return result

¿Hay formas más eficientes de hacerlo? De acuerdo con% timeit, el tiempo de ejecución de la función es 1.26us, mientras se accede al diccionario de la manera estándar como esta

foo = mydict[''first''][''second''][''third''][''fourth'']

lleva 541ns. Estoy buscando maneras de recortarlo a un rango de 800 ns si es posible.

Gracias


Aquí hay una solución similar a la de chrisz, pero no tiene que hacer nada para su dictamen anterior. :

class dictDotter(dict): def __getattr__(self,key): val = self[key] return val if type(val) != dict else dictDotter(val)

y solo x=dictDotter(originalDict) le permitirá obtener puntos arbitrarios (`x.first.second ...). Notaré que esto es dos veces más lento que la solución chrisz, y su es 9 veces más lento que el tuyo (en mi máquina, aproximadamente).

Por lo tanto, si insiste en hacer que esto funcione, @tdelaney parece haber proporcionado la única mejora real del rendimiento.

Otra opción que funciona mejor que lo que tienes (en términos de tiempo de ejecución):

class dictObjecter: def __init__(self,adict): for k,v in adict.items(): self.__dict__[k] = v if type(v) == dict: self.__dict__[k] = dictObjecter(v)

lo que hará que un objeto salga de tu dictado, por lo que la notación con puntos es habitual. Esto mejorará el tiempo de ejecución a 3 veces lo que tiene , así que no está mal, pero al costo de revisar su dictamen y reemplazarlo con otra cosa.

Aquí está el código de prueba total:

from timeit import timeit class dictObjecter: def __init__(self,adict): for k,v in adict.items(): self.__dict__[k] = v if type(v) == dict: self.__dict__[k] = dictObjecter(v) class dictDotter(dict): def __getattr__(self,key): val = self[key] return val if type(val) != dict else dictDotter(val) def get_entry(dict, keyspec): keys = keyspec.split(''.'') result = dict[keys[0]] for key in keys[1:]: result = result[key] return result class dotdict(dict): """dot.notation access to dictionary attributes""" __getattr__ = dict.get __setattr__ = dict.__setitem__ __delattr__ = dict.__delitem__ def dct_to_dotdct(d): if isinstance(d, dict): d = dotdict({k: dct_to_dotdct(v) for k, v in d.items()}) return d x = {''a'':{''b'':{''c'':{''d'':1}}}} y = dictDotter(x) z = dct_to_dotdct(x) w = dictObjecter(x) print(''{:15} : {}''.format(''dict dotter'',timeit(''y.a.b.c.d'',globals=locals(),number=1000))) print(''{:15} : {}''.format(''dot dict'',timeit(''z.a.b.c.d'',globals=locals(),number=1000))) print(''{:15} : {}''.format(''dict objecter'',timeit(''w.a.b.c.d'',globals=locals(),number=1000))) print(''{:15} : {}''.format(''original'',timeit("get_entry(x,''a.b.c.d'')",globals=locals(),number=1000))) print(''{:15} : {:.20f}''.format(''best ref'',timeit("x[''a''][''b''][''c''][''d'']",globals=locals(),number=1000)))

Proporcioné la última búsqueda regular como una mejor referencia. Los resultados en un subsistema Ubuntu de Windows:

dict dotter : 0.0035500000003594323 dot dict : 0.0017939999997906853 dict objecter : 0.00021699999979318818 original : 0.0006629999998040148 best ref : 0.00007999999979801942

por lo tanto, el dictamen está objetivado es 3 veces más lento que una búsqueda en un diccionario regular. Entonces, si la velocidad es importante, ¿por qué querría esto?


Obtuve un aumento del 20% en el rendimiento al ajustar un poco el código, pero un enorme aumento del 400% al usar un caché para cadenas divididas. Eso solo hace una diferencia si usas la misma especificación varias veces. Aquí están las implementaciones de muestra y un script de perfil para probar.

test.py

mydict = { ''first'': { ''second'': { ''third'': { ''fourth'': ''the end'' } } } } # original def get_entry(dict, keyspec): keys = keyspec.split(''.'') result = dict[keys[0]] for key in keys[1:]: result = result[key] return result # tighten up code def get_entry_2(mydict, keyspec): for key in keyspec.split(''.''): mydict = mydict[key] return mydict # use a cache cache = {} def get_entry_3(mydict, keyspec): global cache try: spec = cache[keyspec] except KeyError: spec = tuple(keyspec.split(''.'')) cache[keyspec] = spec for key in spec: mydict = mydict[key] return mydict if __name__ == "__main__": test = get_entry(mydict, ''first.second.third.fourth'') print(test)

perfil.py

from timeit import timeit print("original get_entry") print(timeit("get_entry(mydict, ''first.second.third.fourth'')", setup="from test import get_entry, mydict")) print("get_entry_2 with tighter code") print(timeit("get_entry_2(mydict, ''first.second.third.fourth'')", setup="from test import get_entry_2, mydict")) print("get_entry_3 with cache of split spec") print(timeit("get_entry_3(mydict, ''first.second.third.fourth'')", setup="from test import get_entry_3, mydict")) print("just splitting a spec") print(timeit("x.split(''.'')", setup="x=''first.second.third.fourth''"))

El tiempo en mi máquina es

original get_entry 4.148535753000033 get_entry_2 with tighter code 3.2986323120003362 get_entry_3 with cache of split spec 1.3073233439990872 just splitting a spec 1.0949148639992927

Observe que dividir la especificación es una operación comparativamente costosa para esta función. Es por eso que el almacenamiento en caché ayuda.


Puedes usar reduce ( functools.reduce en python3):

import operator def get_entry(dct, keyspec): return reduce(operator.getitem, keyspec.split(''.''), dct)

Tiene un aspecto más agradable pero con un poco menos de rendimiento.

Tu versión timeit:

>>> timeit("get_entry_original(mydict, ''first.second.third.fourth'')", "from __main__ import get_entry_original, mydict", number=1000000) 0.5646841526031494

con reducir:

>>> timeit("get_entry(mydict, ''first.second.third.fourth'')", "from __main__ import get_entry, mydict") 0.6140949726104736

Como se ha dicho, la división consume casi tanto poder de la CPU como obtener la clave en dict:

def split_keys(keyspec): keys = keyspec.split(''.'') timeit("split_keys(''first.second.third.fourth'')", "from __main__ import split_keys") 0.28857898712158203

Simplemente mueva la división de get_entry función get_entry :

def get_entry(dct, keyspec_list): return reduce(operator.getitem, keyspec_list, dct) timeit("get_entry(mydict, [''first'', ''second'', ''third'', ''fourth''])", "from __main__ import get_entry, mydict") 0.37825703620910645


Realmente hay una sola solución. Reconstruye tu diccionario. Pero hazlo solo una vez.

def recursive_flatten(mydict): d = {} for k, v in mydict.items(): if isinstance(v, dict): for k2, v2 in recursive_flatten(v).items(): d[k + ''.'' + k2] = v2 else: d[k] = v return d

In [786]: new_dict = recursive_flatten(mydict); new_dict Out[786]: {''first.second.third.fourth'': ''the end''}

(Algunas pruebas más)

In [788]: recursive_flatten({''x'' : {''y'' : 1, ''z'' : 2}, ''y'' : {''a'' : 5}, ''z'' : 2}) Out[788]: {''x.y'': 1, ''x.z'': 2, ''y.a'': 5, ''z'': 2} In [789]: recursive_flatten({''x'' : 1, ''y'' : {''x'' : 234}}) Out[789]: {''x'': 1, ''y.x'': 234}

Cada acceso se convierte en tiempo constante de aquí en adelante.

Ahora, simplemente acceda a su valor usando new_dict[''first.second.third.fourth''] . Debería funcionar para cualquier diccionario anidado arbitrariamente que no contenga una auto-referencia.

Tenga en cuenta que cada solución tiene su parte justa de compensaciones, esto no es una excepción. A menos que esté disparando millones de consultas a sus datos, de modo que el procesamiento previo sea una sobrecarga aceptable, entonces esto es todo. Con las otras soluciones, solo está esquivando el problema en lugar de resolverlo, que es tratar con la estructura del diccionario. OTOH, si va a hacer esto una vez en muchas estructuras de datos similares, no tiene sentido realizar un proceso previo solo para una consulta, en cuyo caso puede preferir una de las otras soluciones.


Tenía la misma necesidad, así que creé el Prodict .

Para su caso, puede hacerlo en una línea:

mydict = { ''first'': { ''second'': { ''third'': { ''fourth'': ''the end'' } } } } dotdict = Prodict.from_dict(mydict) print(dotdict.first.second.third.fourth) # "the end"

Después de eso, use dotdict como un dict, porque es una subclase de dict:

dotdict.first == dotdict[''first''] # True

También puede agregar más claves dinámicamente con notación de puntos:

dotdict.new_key = ''hooray'' print(dotdict.new_key) # "hooray"

Funciona incluso si las nuevas claves son diccionarios anidados:

dotdict.it = {''just'': ''works''} print(dotdict.it.just) # "works"

Por último, si define sus claves de antemano, obtendrá la finalización automática y la conversión automática de tipos:

class User(Prodict): user_id: int name: str user = User(user_id="1", "name":"Ramazan") type(user.user_id) # <class ''int''> # IDE will be able to auto complete ''user_id'' and ''name'' properties

ACTUALIZACIÓN :

Este es el resultado de la prueba para el mismo código escrito por @kabanus:

x = {''a'': {''b'': {''c'': {''d'': 1}}}} y = dictDotter(x) z = dct_to_dotdct(x) w = dictObjecter(x) p = Prodict.from_dict(x) print(''{:15} : {}''.format(''dict dotter'', timeit(''y.a.b.c.d'', globals=locals(), number=10000))) print(''{:15} : {}''.format(''prodict'', timeit(''p.a.b.c.d'', globals=locals(), number=10000))) print(''{:15} : {}''.format(''dot dict'', timeit(''z.a.b.c.d'', globals=locals(), number=10000))) print(''{:15} : {}''.format(''dict objecter'', timeit(''w.a.b.c.d'', globals=locals(), number=10000))) print(''{:15} : {}''.format(''original'', timeit("get_entry(x,''a.b.c.d'')", globals=locals(), number=10000))) print(''{:15} : {:.20f}''.format(''prodict getitem'', timeit("p[''a''][''b''][''c''][''d'']", globals=locals(), number=10000))) print(''{:15} : {:.20f}''.format(''best ref'', timeit("x[''a''][''b''][''c''][''d'']", globals=locals(), number=10000)))

Y resultados:

dict dotter : 0.04535976458466595 prodict : 0.02860781018446784 dot dict : 0.019078164088831673 dict objecter : 0.0017378700050722368 original : 0.006594238310349346 prodict getitem : 0.00510931794975705289 best ref : 0.00121740293554022105

Como puede ver, su rendimiento es entre "dict dotter" y "dot dict". Cualquier sugerencia de mejora de rendimiento será apreciada.


¡El código debe ser menos iterativo y más dinámico!

datos

mydict = { ''first'': { ''second'': { ''third'': { ''fourth'': ''the end'' } } } }

Función

def get_entry(dict, keyspec): for keys in keyspec.split(''.''): dict = dict[keys] return dict

llamar a la función

res = get_entry(mydict, ''first.second.third.fourth'')

¡Esto llevará menos tiempo para ejecutarse, incluso si se trata de una ejecución de código dinámico!