Question

Today, I saw one statement which didn't throw an exception. Can anyone explain the theory behind it?

>>> x, y = {'a': 2, 'b': 5}
>>> x
'a'
>>> y
'b'
Was it helpful?

Solution

In Python, every iterable can be unpacked1:

>>> x,y,z = [1, 2, 3]  # A list
>>> x,y,z
(1, 2, 3)
>>> x,y,z = 1, 2, 3  # A tuple
>>> x,y,z
(1, 2, 3)
>>> x,y,z = {1:'a', 2:'b', 3:'c'}  # A dictionary
>>> x,y,z
(1, 2, 3)
>>> x,y,z = (a for a in (1, 2, 3))  # A generator
>>> x,y,z
(1, 2, 3)
>>>

Moreover, because iterating over a dictionary returns only its keys:

>>> for i in {1:'a', 2:'b', 3:'c'}:
...     print i
...
1
2
3
>>>

unpacking a dictionary (which iterates over it) likewise unpacks only its keys.


1Actually, I should say that every iterable can be unpacked as long as the names to unpack into equals the length of the iterable:

>>> a,b,c = [1, 2, 3]  # Number of names == len(iterable)
>>>
>>> a,b = [1, 2, 3]  # Too few names
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: too many values to unpack (expected 2)
>>>
>>> a,b,c,d = [1, 2, 3]  # Too many names
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: need more than 3 values to unpack
>>>

But this is only the case for Python 2.x. In Python 3.x, you have extended iterable unpacking, which allows you to unpack an iterable of any (finite) size into just the names you need:

>>> # Python 3.x interpreter
...
>>> a, *b, c = [1, 2, 3, 4]
>>> a, b, c
(1, [2, 3], 4)
>>>
>>> a, *b = [1, 2, 3, 4]
>>> a, b
(1, [2, 3, 4])
>>>
>>> *a, b, c = [1, 2, 3, 4]
>>> a, b, c
([1, 2], 3, 4)
>>>

OTHER TIPS

Iterating a dict iterates over the keys. Since your dict literal has exactly two keys, you can unpack it into a 2-tuple.

This is probably not a good practice in general, since (before python 3.7, or possibly earlier in some other implementations) dicts are unordered and x == 'b' and y == 'a' would be a perfectly legal outcome of that code.

when you iterate over a dictionary, you get its keys

data = {'a': 2, 'b': 5}
for key in data:
    print key

Unpacking is nothing else than iterating over the object and put the elements in the given variables:

keys = tuple(data) # gives ('a', 'b')
x, y = ('a', 'b')

No rocket science behind it. dict is an iterable, which return the keys in each iteration. tuple() can receive any iterable as argument (as long as they are finite), so:

>>>tuple({'a': 2, 'b': 5})
('a','b')

Seeing this, is easy to infer that unpacking will work as shown. Moreover, any finite iterable can be unpacked:

>>> i = iter(range(3))
>>> a,b,c = i
>>> a,b,c
(0, 1, 2)

When in iterable context, dicts are treated as an (unordered) collection of keys, which is what you get when you do list(some_dict), which is the same as calling keys() on the dict:

>>> d = {'a': 3, 'b': 5}
>>> list(d)
['a', 'b']
>>> d.keys()
['a', 'b']

However, you can also do more.

You can unpack both a dict's both keys and values if you turn it into a list of pairs first:

>>> d = {'a': 3, 'b': 5}
>>> d_pairs = d.items()
>>> print d_pairs
[('a', 3), ('b', 5)]
>>> ((k1, v1), (k2, v2)) = d_pairs
>>> print k1, v1, k2, v2
a 3 b 5

or if you just want the pairs

>>> p1, p2 = d_pairs
>>> print p1, p2
('a', 3) ('b', 5)

or, say, just the keys:

>>> ((k1, _), (k2, _)) = d_pairs
>>> print k1, k2
a b

etc.

But of course since dictionaries — and I mean in general, not only in Python — contain their items in an un-ordered manner, items() (in Python) will also return them in a seemingly arbitrary order, and thus there is no way to know which key will be stored in which variable:

>>> ((k1, v1), (k2, v2)) = {'bar': 3, 'foo': 5}.items()
>>> print k1, v1, k2, v2
foo 5 bar 3

As you see, the order of the pairs returned by items() was reversed in comparison to their definition order.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top