[Python-ideas] Unpacking a dict

Steven D'Aprano steve at pearwood.info
Wed May 25 21:56:07 EDT 2016


On Wed, May 25, 2016 at 01:11:35PM +0000, Michael Selik wrote:

> Clojure also supports mapping destructuring. Let's add that to Python!
> 
>     py> mapping = {"a": 1, "b": 2, "c": 3}
>     py> {"a": x, "b": y, "c": z} = mapping
>     py> x, y, z
>     (1, 2, 3)
>     py> {"a": x, "b": y} = mapping
>     Traceback:
>     ValueError: too many keys to unpack
> 
> 
> This will be approximately as helpful as iterable unpacking was before PEP
> 3132 (https://www.python.org/dev/peps/pep-3132/).

What is your evidence for this claim? So far I've only seen one real- 
world use-case for this, and that single use-case would be well served 
by a simpler syntax:

a, b, c = **mapping

which just requires that a, b, c etc are legal names (not general 
identifiers). The dict is then unpacked:

a = mapping['a']

etc. Two questions:

(1) For the use-case we've already seen, a "preferences" or "settings" 
mapping, do you think users will be prepared to use your syntax?

(2) Do you have any other concrete use cases for this, and if so, what 
are they?


For (1), I've taken a prefs dict from a rather small command line script 
I've written. This is taken from actual code in use. To unpack the dict 
using your syntax, I would have to write:

{'sort': sort, 'reverse': reverse, 'classify': classify, 
 'showlinks': showlinks, 'showsize': showsize, 'spacer': spacer, 
 'style': style, 'width': width} = prefs
# next line is optional, but I might not wish to pollute the namespace
del sort, reverse, showlinks, showsize, style

in order to unpack the three keys I actually want. Here's my suggestion:

classify, spacer, width = **prefs

I don't know about you, but there's no way I'd use your suggested 
syntax as-shown. I'd rather unpack manually:

classify, spacer, width = [prefs[key] for key in 'classify spacer width'.split()]

or variations of same.


For (2), here's another use-case I can think of. Unpacking **kwargs in 
functions/methods.

def spam(self, a, b, **kwargs):
    ...

I'd like to unpack a small number of keys:values from kwargs, extract 
them from the dict, and pass that on to another method:

    fnord = kwargs.pop('fnord', 'default')
    wibble = kwargs.pop('wibble', 42)
    super().spam(a, b, **kwargs)


I don't have a concise syntax for this use-case, and yours won't work 
either. There's no way to supply defaults, nor can you list *all* the 
keys because you don't know what they will be. (The caller can provide 
arbitrary keyword arguments.)

So I don't think this use-case can be handled by either your syntax or 
mine for dict unpacking.


I think your syntax is too verbose and repetitive for the simple case. 
It does have the advantage that it can deal with keys which aren't 
identifiers:

{'while': while_, 'foo bar': foobar} = mapping

but it only looks good in toy examples. In real code, I wouldn't use it, 
it would be too painful and repetitive.

If we add syntax to collect all the unused items, your syntax will be a 
bit less painful, but still repetitive:

{'classify': classify, 'spacer': spacer, 'width': width, **whocares} = prefs

but that has no advantage over what we already have:

classify, spacer, width = [prefs[key] for key in ('classify', 'spacer', 'width')]

(The two are almost the same length, and equally repetitive.)

As far as changing names, we can already do that, and use arbitrary 
references:

myobj.attr['key'][1], while_, foobar = [
        mapping[key] for key in ('something', 'while', 'foo bar')]


So I think that the problems your syntax solve are already easy to 
solve, and the things which are annoying to solve now, your syntax is 
too painful to use.

I'd rather have syntax which is less general but more useful in 
practice, than something which solves dict unpacking in its full 
generality but a pain to use.



-- 
Steve


More information about the Python-ideas mailing list