Desestructurar-enlazar contenidos del diccionario

84

Estoy tratando de 'desestructurar' un diccionario y asociar valores con nombres de variables después de sus claves. Algo como

params = {'a':1,'b':2}
a,b = params.values()

Pero dado que los diccionarios no están ordenados, no hay garantía de que params.values()devuelvan valores en el orden de (a, b). ¿Hay alguna forma agradable de hacer esto?

hatmatrix
fuente
3
¿Perezoso? Quizás ... pero, por supuesto, he mostrado el caso más simple a modo de ilustración. Idealmente, quería tener como para x en params.items: eval ('% s =% f'% x) pero supongo que eval () no permite asignaciones.
hatmatrix
7
@JochenRitzel Estoy bastante seguro de la mayoría de los usuarios de ES6 (JavaScript) le gusta la nueva sintaxis de objetos desestructuración: let {a, b} = params. Mejora la legibilidad y está completamente en línea con cualquier Zen del que quieras hablar.
Andy
8
@Andy Me encanta la desestructuración de objetos en JS. Qué forma tan limpia, simple y legible de extraer algunas claves de un dictado. Vine aquí con la esperanza de encontrar algo similar en Python.
Rotareti
2
También me encanta la desestructuración de objetos de ES6, pero me temo que no puede funcionar en Python por la misma razón que el objeto Map de ES6 no admite la desestructuración. Las claves no son solo cadenas en ES6 Map y Python dict. Además, aunque me encanta el estilo "desplumar" de la desestructuración de objetos en ES6, el estilo de asignación no es simple. ¿Que está pasando aqui? let {a: waffles} = params. Se tarda unos segundos en averiguarlo, incluso si está acostumbrado.
John Christopher Jones
1
@ naught101 Situacionalmente útil con desagradables sorpresas a cambio de compensaciones. Para los usuarios: en Python cualquier objeto puede proporcionar sus propios métodos str / repr. Incluso podría ser tentador hacer esto para objetos clave ligeramente complejos (por ejemplo, tuplas con nombre) para facilitar la serialización JSON. Ahora te quedas rascándote la cabeza por qué no puedes desestructurar una llave por su nombre. Además, ¿por qué funciona esto para artículos pero no para attrs? Muchas bibliotecas prefieren los atributos. Para los implementadores, esta característica de ES6 confunde símbolos (nombres enlazables) y cadenas: razonable en JavaScript, pero Python tiene ideas mucho más ricas en juego. Además, se vería feo.
John Christopher Jones

Respuestas:

10

Si tiene miedo de los problemas relacionados con el uso del diccionario local y prefiere seguir su estrategia original, los Diccionarios ordenados de las colecciones Python 2.7 y 3.1.OrderedDicts le permite recuperar los elementos del diccionario en el orden en que se insertaron por primera vez

Joaquín
fuente
@Stephen, tienes suerte porque OrderedDicts se ha trasladado a python 2.7 desde python 3.1
joaquin
Espero que...! Pensé que siempre fue un punto de fricción para Python para mí (que los diccionarios ordenados no venían cargados con las otras baterías)
hatmatrix
4
Actualmente, en 3.5+, todos los diccionarios están ordenados. Esto no está "garantizado" todavía, lo que significa que podría cambiar.
Charles Merriam
3
Está garantizado desde 3.6+.
naught101
118
from operator import itemgetter

params = {'a': 1, 'b': 2}

a, b = itemgetter('a', 'b')(params)

En lugar de elaboradas funciones lambda o comprensión de diccionario, también puede utilizar una biblioteca incorporada.

Zachary822
fuente
9
Esta probablemente debería ser la respuesta aceptada, ya que es la forma más Pythonic de hacerlo. Incluso puede extender la respuesta para usar attrgetterdesde el mismo módulo de biblioteca estándar, que funciona para atributos de objeto ( obj.a). Esta es una distinción importante de JavaScript, donde obj.a === obj["a"].
John Christopher Jones
Si la clave no existe en el diccionario KeyError, se generará una excepción
Tasawar Hussain
2
Pero ahora está escribiendo ayb dos veces en la declaración de desestructuración
Otto
1
@JohnChristopherJones Eso no me parece muy natural, usar material existente no significa que lo haga comprensible. Dudo que mucha gente lo entienda instantáneamente en un código real. Por otro lado, como se sugiere a, b = [d[k] for k in ('a','b')]es mucho más natural / legible (el formulario es mucho más común). Esta sigue siendo una respuesta interesante, pero no es la solución más sencilla.
cglacet
@cglacet Creo que depende de cuántas veces tengas que leer / implementar eso. Si eliges habitualmente las mismas 3 teclas, entonces es más sencillo get_id = itemgetter(KEYS)usar algo así como después serial, code, ts = get_id(document). Es cierto que debe sentirse cómodo con las funciones de orden superior, pero Python generalmente se siente muy cómodo con ellas. Por ejemplo, consulte los documentos para decoradores como @contextmanager.
John Christopher Jones
28

Una forma de hacer esto con menos repetición que la sugerencia de Jochen es con una función de ayuda. Esto le da la flexibilidad de enumerar sus nombres de variables en cualquier orden y solo desestructurar un subconjunto de lo que está en el dict:

pluck = lambda dict, *args: (dict[arg] for arg in args)

things = {'blah': 'bleh', 'foo': 'bar'}
foo, blah = pluck(things, 'foo', 'blah')

Además, en lugar del OrderedDict de joaquin, podría ordenar las claves y obtener los valores. Las únicas capturas son que necesita especificar los nombres de sus variables en orden alfabético y desestructurar todo en el dict:

sorted_vals = lambda dict: (t[1] for t in sorted(dict.items()))

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)
ShawnFumo
fuente
4
Esta es una objeción menor, pero si va a asignar una lambdaa una variable, también puede usar la sintaxis de función normal con def.
Arthur Tacca
¿No puedes hacer esto como JS, donde sería const {a, b} = {a: 1, b: 2}?
PirateApp
1
Ha implementado itemgetterdesde operatorla biblioteca estándar. :)
John Christopher Jones
16

Python solo puede "desestructurar" secuencias, no diccionarios. Por lo tanto, para escribir lo que desee, deberá asignar las entradas necesarias a una secuencia adecuada. Por mi parte, la coincidencia más cercana que pude encontrar es la (no muy sexy):

a,b = [d[k] for k in ('a','b')]

Esto también funciona con generadores:

a,b = (d[k] for k in ('a','b'))

Aquí hay un ejemplo completo:

>>> d = dict(a=1,b=2,c=3)
>>> d
{'a': 1, 'c': 3, 'b': 2}
>>> a, b = [d[k] for k in ('a','b')]
>>> a
1
>>> b
2
>>> a, b = (d[k] for k in ('a','b'))
>>> a
1
>>> b
2
Sylvain Leroux
fuente
10

¿Quizás de verdad quieres hacer algo como esto?

def some_func(a, b):
  print a,b

params = {'a':1,'b':2}

some_func(**params) # equiv to some_func(a=1, b=2)
Jochen Ritzel
fuente
Gracias, pero no eso ... Estoy desestructurando dentro de una función
hatmatrix
10

Aquí hay otra forma de hacerlo de manera similar a cómo funciona una asignación de desestructuración en JS:

params = {'b': 2, 'a': 1}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Lo que hicimos fue descomprimir el diccionario de params en valores clave (usando **) (como en la respuesta de Jochen ), luego tomamos esos valores en la firma lambda y los asignamos de acuerdo con el nombre de la clave, y aquí hay una ventaja: nosotros también obtenga un diccionario de lo que no esté en la firma lambda, así que si tuviera:

params = {'b': 2, 'a': 1, 'c': 3}
a, b, rest = (lambda a, b, **rest: (a, b, rest))(**params)

Una vez que se haya aplicado la lambda, la variable rest ahora contendrá: {'c': 3}

Útil para omitir claves innecesarias de un diccionario.

Espero que esto ayude.

Oh Sharv
fuente
Interesante, por otro lado, creo que sería mejor en una función. Probablemente usará esto varias veces y de esa manera también tendrá un nombre. (cuando digo función, quiero decir, no una función lambda).
cglacet
3

prueba esto

d = {'a':'Apple', 'b':'Banana','c':'Carrot'}
a,b,c = [d[k] for k in ('a', 'b','c')]

resultado:

a == 'Apple'
b == 'Banana'
c == 'Carrot'
Junaid
fuente
3

Advertencia 1: como se indica en los documentos, no se garantiza que funcione en todas las implementaciones de Python:

Detalle de la implementación de CPython: esta función se basa en el soporte del marco de pila de Python en el intérprete, que no se garantiza que exista en todas las implementaciones de Python. Si se ejecuta en una implementación sin soporte de marco de pila de Python, esta función devuelve None.

Advertencia 2: esta función hace que el código sea más corto, pero probablemente contradice la filosofía de Python de ser lo más explícito posible. Además, no aborda los problemas señalados por John Christopher Jones en los comentarios, aunque podría crear una función similar que funcione con atributos en lugar de claves. ¡Esto es solo una demostración de que puede hacer eso si realmente lo desea!

def destructure(dict_):
    if not isinstance(dict_, dict):
        raise TypeError(f"{dict_} is not a dict")
    # the parent frame will contain the information about
    # the current line
    parent_frame = inspect.currentframe().f_back

    # so we extract that line (by default the code context
    # only contains the current line)
    (line,) = inspect.getframeinfo(parent_frame).code_context

    # "hello, key = destructure(my_dict)"
    # -> ("hello, key ", "=", " destructure(my_dict)")
    lvalues, _equals, _rvalue = line.strip().partition("=")

    # -> ["hello", "key"]
    keys = [s.strip() for s in lvalues.split(",") if s.strip()]

    if missing := [key for key in keys if key not in dict_]:
        raise KeyError(*missing)

    for key in keys:
        yield dict_[key]
In [5]: my_dict = {"hello": "world", "123": "456", "key": "value"}                                                                                                           

In [6]: hello, key = destructure(my_dict)                                                                                                                                    

In [7]: hello                                                                                                                                                                
Out[7]: 'world'

In [8]: key                                                                                                                                                                  
Out[8]: 'value'

Esta solución le permite elegir algunas de las claves, no todas, como en JavaScript. También es seguro para los diccionarios proporcionados por el usuario.

decorador-fabrica
fuente
1

Bueno, si quieres estos en una clase, siempre puedes hacer esto:

class AttributeDict(dict):
    def __init__(self, *args, **kwargs):
        super(AttributeDict, self).__init__(*args, **kwargs)
        self.__dict__.update(self)

d = AttributeDict(a=1, b=2)
abyx
fuente
Agradable. Gracias, pero parece una forma de cambiar la sintaxis de la llamada de d ['a'] a da? Y quizás agregar métodos que tienen acceso implícito a estos parámetros ...
hatmatrix
0

Basado en la respuesta de @ShawnFumo, se me ocurrió esto:

def destruct(dict): return (t[1] for t in sorted(dict.items()))

d = {'b': 'Banana', 'c': 'Carrot', 'a': 'Apple' }
a, b, c = destruct(d)

(Observe el orden de los elementos en dict)

Ricardo
fuente
0

Dado que los diccionarios son garantiza que los mantendrán su orden de inserción en Python> = 3.7 , eso significa que es completamente seguro e idiomático hacer esto hoy en día:

params = {'a': 1, 'b': 2}
a, b = params.values()
print(a)
print(b)

Salida:

1
2
ruohola
fuente
2
¡Excelente! Solo les tomó 9 años desde mi publicación. :)
hatmatrix
1
El problema es que no puede hacerlo b, a = params.values(), ya que usa orden, no nombres.
Corman
1
@ruohola Ese es el problema con esta solución. Esto se basa en el orden de los diccionarios para los nombres, no en los nombres en sí mismos, razón por la cual lo rechacé.
Corman
1
Y eso lo convierte en una mala solución si se basa en el orden de las claves. itemgetteres la forma más pitónica de hacer esto. Esto no funcionará en Python 3.6 o versiones anteriores y, como depende del orden, puede resultar confuso al principio.
Corman
-1

No sé si es de buen estilo, pero

locals().update(params)

hará el truco. Luego tiene a, by lo que estaba en su paramsdictado disponible como variables locales correspondientes.

Johannes Charra
fuente
2
Tenga en cuenta que esto puede ser un gran problema de seguridad si el diccionario 'params' lo proporciona el usuario de alguna manera y no se filtra correctamente.
Jacek Konieczny
9
Para citar docs.python.org/library/functions.html#locals : Nota: El contenido de este diccionario no debe modificarse; los cambios pueden no afectar los valores de las variables locales y libres utilizadas por el intérprete.
Jochen Ritzel
4
Aprendí la lección. Chicos THX.
Johannes Charra