[Python-ideas] Dictionary destructing and unpacking.

Steven D'Aprano steve at pearwood.info
Wed Jun 7 21:18:06 EDT 2017


On Wed, Jun 07, 2017 at 06:14:08PM +0000, Nick Humrich wrote:

> It would be cool to have a syntax that would unpack the dictionary to
> values based on the names of the variables. Something perhaps like:
> 
> a, b, c = **mydict

This was discussed (briefly, to very little interest) in March/April 
2008:

https://mail.python.org/pipermail/python-ideas/2008-March/001511.html
https://mail.python.org/pipermail/python-ideas/2008-April/001513.html

and then again in 2016, when it spawned a very large thread starting 
here:

https://mail.python.org/pipermail/python-ideas/2016-May/040430.html

I know there's a lot of messages, but I STRONGLY encourage anyone, 
whether you are for or against this idea, to read the previous 
discussion before continuing it here.

Guido was luke-warm about the **mapping syntax:

https://mail.python.org/pipermail/python-ideas/2016-May/040466.html

Nathan Schneider proposed making dict.values() take optional key names:

https://mail.python.org/pipermail/python-ideas/2016-May/040517.html

Guido suggested that this should be a different method:

https://mail.python.org/pipermail/python-ideas/2016-May/040518.html

My recollection is that the discussion evertually petered out with a 
more-or-less consensus that having a dict method (perhaps "getvalues"?) 
plus regular item unpacking is sufficient for the common use-case of 
unpacking a subset of keys:

prefs = {'width': 80, 'height': 200, 'verbose': False, 'mode': PLAIN,
         'name': 'Fnord', 'flags': spam|eggs|cheese, ... }  
         # dict includes many more items

width, height, size = prefs.getvalues(
        'width', 'height', 'papersize',
        )


This trivially supports the cases where keys are not strings or valid 
identifiers:

class_, spam, eggs = mapping.getvalues('class', 42, '~')

It easily supports assignment targets which aren't simple variable 
names:

obj.attribute[index], spam().attr = mapping.getvalues('foo', 'bar')

An optional (defaults to False) "pop" keyword argument supports 
extracting and removing values from the dict in one call, which is 
commonly needed inside __init__ methods with **kwargs:

class K(parent):
    def __init__(self, a, b, c, **kwargs):
        self.spam = kwargs.pop('spam')
        self.eggs = kwargs.pop('eggs')
        self.cheese = kwargs.pop('cheese')
        super().__init__(a, b, c, **kwargs)
        

becomes:

        self.spam, self.eggs, self.cheese = kwargs.getvalues(
                'spam eggs cheese'.split(), pop=True
                )


I don't recall this being proposed at the time, but we could support 
keyword arguments for missing or default values:

DEFAULTS = {'height': 100, 'width': 50}
prefs = get_prefs()  # returns a dict

height, width, size = prefs.getvalues(
        'height', 'width', 'papersize',
        defaults=DEFAULTS,
        missing=None
        )


A basic implementation might be:

# Untested.
def getvalues(self, *keys, pop=False, defaults=None, missing=SENTINEL):
    values = []
    for key in keys:
        try:
            x = self[key]
        except KeyError:
            if defaults is not None:
                x = defaults.get(key, SENTINEL)
            if x is SENTINEL:
                x = missing
            if x is SENTINEL:
                raise KeyError('missing key %r' % key)
        if pop:
            del self[key]
        values.append(x)
    return tuple(values)



It's a bit repetitive for the common case where keys are the same as the 
assignment targets, but that's a hard problem to solve, and besides, 
"explicit is better than implicit".

It also doesn't really work well for the case where you want to blindly create new assignment targets for 
*every* key, but:

- my recollection is that nobody really came up with a convincing 
  use-case for this (apologies if I missed any);

- and if you really need this, you can do:

    locals().update(mapping)

inside a class body or at the top-level of the module (but not inside a 
function).

Please, let's save a lot of discussion here and now, and just read the 
2016 thread: it is extremely comprehensive.


-- 
Steve


More information about the Python-ideas mailing list