63

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'
nik_kgp
  • 1,112
  • 1
  • 9
  • 17
  • 1
    Since it's not mentioned below, I'll say this design decision was likely influenced by utility. When a dict gives you its keys, it is implicitly giving you access to its values. If it returned its values instead, it would be harder to access its keys. – Two-Bit Alchemist Apr 24 '14 at 17:32

5 Answers5

64

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)
>>>
  • I guess, that means every iterable can be unpacked? – aIKid Apr 24 '14 at 12:18
  • 1
    Yes, but it doesn't usually make sense for iterables that are mutable (both in length and content) and even less so for unordered iterables. You would have no way of gaurenteeing what key wound up where, and no way of gaurenteing you provided enough variables to unpack to. – aruisdante Apr 24 '14 at 12:22
  • 2
    @aIKid, not the infinite ones – John La Rooy Apr 24 '14 at 12:23
  • isn't it very confusing, if there is only one key in dict? x={'a':3}, print x gives {'a': 3} while if we do x,={'a':3} and then print x then result is 'a'. – nik_kgp Apr 24 '14 at 12:30
  • @nik_kgp - The first piece of code is assigning the dictionary itself to `x`. The second piece however is _unpacking_ the dictionary into `x`. –  Apr 24 '14 at 12:34
  • @iCodez, ya I do understand that but it seems to be very confusing, that's all. I'd prefer not to use anything like this. – nik_kgp Apr 24 '14 at 12:35
  • In python 3, you can unpack a sequence of any size using the star syntax: `a, *b = [1, 2, 3]` results in `a = 1` and `b = [2, 3]`. If you just want to read in the first part of the sequence you should use [islice](https://docs.python.org/2/library/itertools.html#itertools.islice): `x, y = islice((a for a in range(1000)), 2)` – nmclean Apr 24 '14 at 12:56
  • @nmclean - Good idea. Let me mention the extended iterable unpacking. –  Apr 24 '14 at 13:13
  • @nik_kgp Using it on a dictionary or a single-item collection is unusual, but it's quite handy and natural when you have a well understood sequence like `width, height = size` or when a function returns multiple pieces of information (for example [sys.exc_info](https://docs.python.org/2/library/sys.html#sys.exc_info)). – nmclean Apr 24 '14 at 13:17
  • @nik_kgp `x = a` and `x, = a` are not the same thing, there's an extra character (the comma). Why would you expect the result to be the same? Would you expect `x += 1` and `x = 1` to give the same result? They only differ by a single character as well. – SethMMorton Apr 25 '14 at 02:33
  • @SethMMorton Yup, I agree with you. I just posted my view on it. – nik_kgp Apr 25 '14 at 08:03
20

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.

Wooble
  • 87,717
  • 12
  • 108
  • 131
8

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')
Daniel
  • 42,087
  • 4
  • 55
  • 81
6

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)
Paulo Bu
  • 29,294
  • 6
  • 74
  • 73
5

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.

Erik Kaplun
  • 37,128
  • 15
  • 99
  • 111