Destructuring-bind dictionary contents

Question:

I am trying to ‘destructure’ a dictionary and associate values with variables names after its keys. Something like

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

But since dictionaries are not ordered, there is no guarantee that params.values() will return values in the order of (a, b). Is there a nice way to do this?

Asked By: hatmatrix

||

Answers:

I don’t know whether it’s good style, but

locals().update(params)

will do the trick. You then have a, b and whatever was in your params dict available as corresponding local variables.

Answered By: Johannes Charra

Well, if you want these in a class you can always do this:

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

d = AttributeDict(a=1, b=2)
Answered By: abyx

If you are afraid of the issues involved in the use of the locals dictionary and you prefer to follow your original strategy, Ordered Dictionaries from python 2.7 and 3.1 collections.OrderedDicts allows you to recover you dictionary items in the order in which they were first inserted

Answered By: joaquin

Maybe you really want to do something like this?

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

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

some_func(**params) # equiv to some_func(a=1, b=2)
Answered By: Jochen Ritzel

One way to do this with less repetition than Jochen’s suggestion is with a helper function. This gives the flexibility to list your variable names in any order and only destructure a subset of what is in the dict:

pluck = lambda dict, *args: (dict.get(arg, -1) for arg in args)

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

Also, instead of joaquin’s OrderedDict you could sort the keys and get the values. The only catches are you need to specify your variable names in alphabetical order and destructure everything in the dict:

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

things = {'foo': 'bar', 'blah': 'bleh'}
blah, foo = sorted_vals(things)
Answered By: ShawnFumo

Python is only able to “destructure” sequences, not dictionaries. So, to write what you want, you will have to map the needed entries to a proper sequence. As of myself, the closest match I could find is the (not very sexy):

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

This works with generators too:

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

Here is a full example:

>>> 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
Answered By: Sylvain Leroux

Look for other answers as this won’t cater to the unexpected order in the dictionary. will update this with a correct version sometime soon.

try this

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

result:

a == 'Apple'
b == 'Banana'
c == 'Carrot'
Answered By: Junaid
from operator import itemgetter

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

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

Instead of elaborate lambda functions or dictionary comprehension, may as well use a built in library.

Answered By: Zachary822

Here’s another way to do it similarly to how a destructuring assignment works in JS:

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

What we did was to unpack the params dictionary into key values (using **) (like in Jochen’s answer), then we’ve taken those values in the lambda signature and assigned them according to the key name – and here’s a bonus – we also get a dictionary of whatever is not in the lambda’s signature so if you had:

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

After the lambda has been applied, the rest variable will now contain:
{‘c’: 3}

Useful for omitting unneeded keys from a dictionary.

If you don’t want to preserve the rest, you can do:

a, b = (lambda a, b, **_): (a, b))(**params)
Answered By: O Sharv

Based on @ShawnFumo answer I came up with this:

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

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

(Notice the order of items in dict)

Answered By: Richard

Warning 1: as stated in the docs, this is not guaranteed to work on all Python implementations:

CPython implementation detail: This function relies on Python stack frame support
in the interpreter, which isn’t guaranteed to exist in all implementations
of Python. If running in an implementation without Python stack frame support
this function returns None.

Warning 2: this function does make the code shorter, but it probably contradicts the Python philosophy of being as explicit as you can. Moreover, it doesn’t address the issues pointed out by John Christopher Jones in the comments, although you could make a similar function that works with attributes instead of keys. This is just a demonstration that you can do that if you really want to!

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'

This solution allows you to pick some of the keys, not all, like in JavaScript. It’s also safe for user-provided dictionaries

Answered By: decorator-factory

Since dictionaries are guaranteed to keep their insertion order in Python >= 3.7, that means that it’s complete safe and idiomatic to just do this nowadays:

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

Output:

1
2
Answered By: ruohola

How come nobody posted the simplest approach?

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

a, b = params['a'], params['b']
Answered By: Danon

With Python 3.10, you can do:

d = {"a": 1, "b": 2}

match d:
    case {"a": a, "b": b}:
        print(f"A is {a} and b is {b}")

but it adds two extra levels of indentation, and you still have to repeat the key names.

Answered By: decorator-factory

(Ab)using the import system

The from ... import statement lets us desctructure and bind attribute names of an object. Of course, it only works for objects in the sys.modules dictionary, so one could use a hack like this:

import sys, types

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

sys.modules["mydict"] = types.SimpleNamespace(**mydict)

from mydict import a, b

A somewhat more serious hack would be to write a context manager to load and unload the module:

with obj_as_module(mydict, "mydict_module"):
    from mydict_module import a, b

By pointing the __getattr__ method of the module directly to the __getitem__ method of the dict, the context manager can also avoid using SimpleNamespace(**mydict).

See this answer for an implementation and some extensions of the idea.

One can also temporarily replace the entire sys.modules dict with the dict of interest, and do import a, b without from.

Answered By: Erik

An old topic, but I found this to be a useful method:

data = {'a':'Apple', 'b':'Banana','c':'Carrot'}
for key in data.keys():
    locals()[key] = data[key]

This method loops over every key in your dictionary and sets a variable to that name and then assigns the value from the associated key to this new variable.

Testing:

print(a)
print(b)
print(c)

Output

Apple
Banana
Carrot
Answered By: Peter Dunne

An easy and simple way to destruct dict in python:

params = {"a": 1, "b": 2}
a, b = [params[key] for key in ("a", "b")]
print(a, b)
# Output:
# 1 2
Answered By: Ericgit

No, there is currently no nice way of doing this in Python.

In order to qualify as "nice" in my book, the solution would have to avoid repeating the dictionary keys in the assignment.

Illustration:

from operator import itemgetter

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

# Not nice solution, we still have to spell out 'a_very_long_name' twice
a_very_long_name, b = itemgetter('a_very_long_name', 'b')(params)

Instead, the most readable way to write this in Python is:

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

a_very_long_name = params['a_very_long_name']
b = params['b']

In Javascript, there is a specific syntax for Object destructuring:

const params = {a_very_long_name: 1, b: 2};

const {a_very_long_name, b} = params;

If you have a Javascript background, you may be tempted to look for the same in Python. But Python is not Javascript, and it doesn’t have this feature. This doesn’t make Python inferior, it’s just a different design decision, and the best way to deal with it is to accept it rather than trying to replicate the "Javascript-way" of writing code.

Answered By: ValarDohaeris

Easiest and the best method is

    client_code, password, login_type = (
        body.get("client_code"),
        body.get("password"),
        body.get("login_type"),
    )

The Great Think About .get() is that it will not raise an error, which usually happens in the case of body[‘password’] , by default the .get() will return None

Answered By: Shubham Yadav05
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.