156

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?

double-beep
  • 5,031
  • 17
  • 33
  • 41
hatmatrix
  • 42,883
  • 45
  • 137
  • 231
  • 3
    Lazy? Maybe... but of course I've shown the simplest case for illustration. Ideally I wanted to do have like for x in params.items: eval('%s = %f' % x) but I guess eval() doesn't allow assignments. – hatmatrix Jun 02 '10 at 10:07
  • 24
    @JochenRitzel I'm pretty sure most users of ES6 (JavaScript) likes the new object destructuring syntax: `let {a, b} = params`. It enhances readability and is completely inline with whatever Zen you want to talk about. – Andy Apr 25 '16 at 18:35
  • 30
    @Andy I love object *destructuring* in JS. What a clean, simple and readable way to extract some keys from a dict. I came here with the hope of finding something similar in Python. – Rotareti Feb 17 '17 at 04:04
  • @Rotareti certainly beats Python's tuple destructuring, where one can easily get the order wrong! – Andy Feb 17 '17 at 17:04
  • 4
    I also love ES6 object destructuring, but I'm afraid it can't work in Python for the same reason ES6's Map object doesn't support destructuring. Keys aren't just strings in ES6 Map and Python dict. Also, although I love the "pluck" style of object destructuring in ES6, the assignment style is not simple. What's going on here? `let {a: waffles} = params` . It takes a few seconds to figure it out even if you're used to it. – John Christopher Jones Mar 10 '18 at 00:46
  • @JohnChristopherJones: Does that matter though? The fact that you wouldn't be able to get all keys from all dictionaries doesn't mean that such a syntax wouldn't be useful in a large number of cases. If it only worked for string keys, that would still be very useful. – naught101 Sep 25 '19 at 00:24
  • 1
    @naught101 Situationally useful with nasty surprises for tradeoffs. For users: In Python any object can provide its own str/repr methods. It might even be tempting to do this for slightly complex key objects (e.g., named tuples) for easier JSON serialization. Now you're left scratching your head why you can't destructure a key by name. Also, why does this work for items but not attrs? Lots of libraries prefer attrs. For implementers, this ES6 feature confuses symbols (bindable names) and strings: reasonable in JavaScript but Python has much richer ideas in play. Also, it'd just look ugly. – John Christopher Jones Sep 26 '19 at 05:40
  • Very good points, @JohnChristopherJones, though I disagree about the ugly :) – naught101 Sep 26 '19 at 07:07
  • Quick tidbit, I think you may have the wrong ideas about what `not ordered` means. Its very likely that for the scenario you're working on, the example you provided will be sufficient. – Jamie Marshall Jul 31 '22 at 23:16
  • All reasonable criticisms of ES6 like destructuring syntax, but there is precedent in the `dict(**kwargs)` constructor: `dict(key1=val1)` is valid, while e.g. `dict(100=1)` is not. – jnj16180340 May 05 '23 at 22:42

18 Answers18

270
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.

Zachary822
  • 2,873
  • 2
  • 11
  • 9
  • 22
    This should probably be the accepted answer, as it's the most Pythonic way to do it. You can even extend the answer to use `attrgetter` from the same standard library module, which works for object attributes (`obj.a`). This is a major distinction from JavaScript, where `obj.a === obj["a"]`. – John Christopher Jones Sep 26 '19 at 05:47
  • 2
    If the key does not exist on dictionary `KeyError` exception will be raised – Tasawar Hussain Apr 09 '20 at 20:13
  • 10
    But you are typing a and b twice now in the destructuring statement – Otto May 15 '20 at 09:05
  • 4
    @JohnChristopherJones That doesn't seem very natural to me, using existing stuff doesn't mean it makes it understandable. I doubt many people would instantly understand in a real code. On the other hand, as suggested `a, b = [d[k] for k in ('a','b')]` is way more natural/readable (the form is way more common). This is still an interesting answer, but that's not the most straightforward solution. – cglacet Jul 19 '20 at 14:35
  • @cglacet I think it depends on how many times you have to read/implement that. If you routinely pick out the same 3 keys, then something like `get_id = itemgetter(KEYS)` then later using `serial, code, ts = get_id(document)` is simpler. Admittedly, you do have to be comfortable with higher-order functions, but Python is generally very comfortable with them. E.g., see the docs for decorators like `@contextmanager`. – John Christopher Jones Sep 24 '20 at 18:18
  • 4
    @TasawarHussain Thank god it throws `KeyError`. What else should it do? – Danon Sep 05 '21 at 10:09
44

How come nobody posted the simplest approach?

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

a, b = params['a'], params['b']
Danon
  • 2,771
  • 27
  • 37
39

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[arg] 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)
ShawnFumo
  • 2,108
  • 1
  • 25
  • 14
27

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
Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
23

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.

Hope this helps.

O Sharv
  • 353
  • 2
  • 8
  • 1
    Interesting, on the other hand I feel it would be better in a function. You'll probably use this several time and that way you'll have a name on it too. (when I say function I mean, not a lambda function). – cglacet Jul 19 '20 at 14:38
  • There is no way you can move it to a function, as it relies on you using the same identifier in the lefthand side of the assignment and in the lambda arguments. – Rodrigo Rodrigues Jun 11 '23 at 03:29
15

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)
Jochen Ritzel
  • 104,512
  • 31
  • 200
  • 194
  • Thanks, but not that... I'm destructuring within a function – hatmatrix Jun 02 '10 at 10:01
  • @hatmatrix and it seems that it's the case where you can create function that makes your code cleaner – Karolius May 07 '21 at 08:48
  • 1
    IMO, this answer adheres to what OP explicitly asked for and it's using language features appropriately. The only compromise here is that the control flow is moved to a function – darw Nov 02 '21 at 15:19
  • No one who had used javascript destructuring would think this was a reasonable substitute. Not only does it use four lines to achieve (instead of the "direct" implementation which takes two), it pollutes the namespace with a new function you're not going to use again, it requires a new function for each "kind" of destructuring you're doing, and it _still_ requires you to put the arguments in corresponding order with each invocation, meaning it provides no typo safety. You're better off just writing `a = data['a'], b = data['b']` than using this. – Richard Rast Aug 09 '23 at 14:56
  • 1
    @RichardRast no, this does NOT require corresponding order. As the comment says, `**params` is equivalent to Python's _named_ param passing whose whole point is NOT caring about order. `some_func(a=1, b=2)` == `some_func(b=2, a=1)`. – Beni Cherniavsky-Paskin Aug 21 '23 at 14:57
  • A limitation of this style is that it's not compactly nestable. Python 3 [removed tuple arguments](https://peps.python.org/pep-3113/) and those were unnamed anyway. So if you have say a dict of lists of dicts, what you'll end up writing will not _look like construction_ `{ "key": [ { "inner_key": var } ] }`, it'll involve one level of destructuring at a time — an outer function `def f1(key):` then inside it `[ item ] = key` then an inner function `def f2(inner_key): ...` – Beni Cherniavsky-Paskin Aug 21 '23 at 15:10
11

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

joaquin
  • 82,968
  • 29
  • 138
  • 152
  • 7
    Currently, in 3.5+, all dictionaries are ordered. This is not "guaranteed" yet, meaning it could change. – Charles Merriam Dec 15 '16 at 17:26
  • 9
    It's guaranteed from 3.6+. – naught101 Sep 25 '19 at 00:03
  • @Zachary822's answer, though requiring a little more code, has the advantage that you don't have to know (or remember) the order of the contents of the dictionary and it won't break if it ever changes — so it's worth being aware of _both_ ways this can be done IMO. – martineau May 12 '21 at 11:19
8

(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.

Erik
  • 2,500
  • 2
  • 13
  • 26
  • 1
    If this is not enough hacks for one's taste, you can also use module-level `__getattr__` :) – decorator-factory Feb 09 '23 at 09:31
  • 1
    "temporarily replace the entire `sys.modules`" is too risky if using threads. Using any of these in a function is also risky if it can be invoked from multiple threads; at top level is safer. But, very clever observation — import...from syntax indeed has a destructuring quality :-) – Beni Cherniavsky-Paskin Aug 21 '23 at 14:51
7

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.

decorator-factory
  • 2,733
  • 13
  • 25
6

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

decorator-factory
  • 2,733
  • 13
  • 25
3

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'
Junaid
  • 4,682
  • 1
  • 34
  • 40
  • Comments below [this](https://stackoverflow.com/a/2955476/143476) answer suggests modifying locals() is discouraged. – hatmatrix Dec 02 '21 at 00:37
  • That's right because creating variables from data itself is a security risk. But I believe my code is not doing that. – Junaid Dec 02 '21 at 08:16
1

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)
abyx
  • 69,862
  • 18
  • 95
  • 117
  • Nice. Thanks, but seems like a way to change the call syntax from d['a'] to d.a? And perhaps adding methods that have implicitly access to these parameters... – hatmatrix Jun 02 '10 at 10:04
1

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)

Richard
  • 14,427
  • 9
  • 57
  • 85
1

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
  • Be aware the locals dictionary is not meant to be modified, see this [comment](https://stackoverflow.com/questions/2955412/destructuring-bind-dictionary-contents?noredirect=1&lq=1#comment3013067_2955476) to another answer. – Erik Apr 16 '22 at 18:56
0

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
Ericgit
  • 6,089
  • 2
  • 42
  • 53
0

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.

ValarDohaeris
  • 6,064
  • 5
  • 31
  • 43
-1

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.

Johannes Charra
  • 29,455
  • 6
  • 42
  • 51
  • 2
    Please note this may be a big security problem if the 'params' dictionary is user-provided in any way and not properly filtered. – Jacek Konieczny Jun 02 '10 at 06:38
  • 11
    To quote http://docs.python.org/library/functions.html#locals: *Note: The contents of this dictionary should not be modified; changes may not affect the values of local and free variables used by the interpreter.* – Jochen Ritzel Jun 02 '10 at 06:51
-2

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
ruohola
  • 21,987
  • 6
  • 62
  • 97
  • 7
    The issue is that you can't do `b, a = params.values()`, since it uses order not names. – Corman Jul 06 '20 at 06:44
  • 2
    @ruohola That's the issue with this solution. This relies on the order of dictionaries for names, not the names themselves, which is why I downvoted this. – Corman Jul 06 '20 at 19:13
  • 1
    And that makes it a bad solution if it relies on order of the keys. `itemgetter` is the most pythonic way to do this. This won't work on Python 3.6 or below, and because it relies on order, it can be confusing at first. – Corman Jul 06 '20 at 21:36
  • 1
    Please, do not use this solution anywhere since it is a source of hard-to-find bugs. – SergeyR Apr 06 '21 at 13:03
  • 1
    @SergeyR Yes I agree, but it answers OP's concern quite directly `But since dictionaries are not ordered, there is no guarantee that params.values() will return values in the order of (a, b).` – ruohola Apr 07 '21 at 15:14