Multiple key access for dicts and dict-like objects

This is just an idea, but I think it would be nice to allow dicts and dict-like objects to allow multiple keys to be accessed at once, using a special iterable type operator <..> and a listing of requested keys defined with the operator. Here’s an example with dict below, with requested keys provided in a list type:

>>> di = {'a': 1, 'b': 2, 'c': 3}
>>> di[list<'a', 'c'>]
[1, 3]                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            

And the same example with a tuple type:

>>> di[tuple<'a', 'b'>]
(1, 2,)

If the keys don’t exist a KeyError could be raised, same for a partial match. For an empty dict the return could be an empty iterable of the given type.

This proposal will have to out-compete comprehensions to have any hope of going anywhere:

>>> di = {'a': 1, 'b': 2, 'c': 3}
>>> [di[k] for k in ('a', 'c')]
[1, 3]
>>> tuple(di[k] for k in ('a', 'b'))
(1, 2)

If keys do not exist it raises KeyError.

You can do the same using list/tuple/generator comprehension:

di = {'a': 1, 'b': 2, 'c': 3}
# values = [di[k] for k in ['a', 'c', 'd'] if k in di]  # list
values = tuple(di[k] for k in ['a', 'c', 'd'] if k in di)  # tuple
# values = [di[k] for k in ['a', 'c', 'd']]  # KeyError

print(values, type(values))

values = (di[k] for k in ['a', 'c', 'd'] if k in di)  # generator

It’s not clear how to pass a variable:

keys = tuple('a', 'c', 'd')

It looks like tuple is redundant in di[tuple<keys>] since keys is already a tuple. Using unpacking operator “*” seems more reasonable:

Here is an example using a dict subclass:

class D(dict):
    def __getitem__(self, keys):
        return type(keys)(super(D, self).__getitem__(key) for key in keys)

di = {'a': 1, 'b': 2, 'c': 3}
keys = ('a', 'c',)
d = D(di)
print(d[keys])  # (1, 3)
1 Like

One could also use di.get(k, default) for k in keys.

There is no need for something to duplicate what we have, which is extremely flexible.


If you can live with stringifying your keys instead of introducing a new <..> syntax, and performance isn’t critical (e.g. when parsing configs), you could look at my namespacedict package, which does full AST parsing of the keys for getting and setting.

When performance matters, you do generally want a specific comprehension, as suggested above.

Yes, obviously, comprehensions are the first thing that spring to mind, and they’ll do the job fine, but I was thinking of this idea as a way to provide a built-in syntactic shortcut for dealing with the situation I’ve described. This is actually motivated by Pandas dataframes where you can get slices easily using the square brackets [[..]]. Comprehensions involve iteration, and I don’t want to write an iteration if I don’t have to.

The <..> notation does look strange, I guess, and perhaps going with the dataframes theme, square brackets would look nice, but of course lists can’t be dict keys, so that wouldn’t work.

On passing in the keys as a variable, you have a valid point on the problem of specifying type. I don’t know.

@elis.byberi The custom dict subclass is nice, thanks!

Looks interesting, thanks :slight_smile:

I don’t see any argument for new syntax here. It might be useful to have a dict method that accesses a set of indexes although there is already a builtin way to access multiple keys from a dict without writing a list comprehension so even a method doesn’t really add anything new:

>>> from operator import itemgetter
>>> di = {'a': 1, 'b': 2, 'c': 3}
>>> itemgetter('a', 'c')(di)
(1, 3)

It looks a bit strange as you have the double call but it does work and you can assign the itemgetter to a variable and reuse it.