132

I have a dictionary, and would like to pass a part of it to a function, that part being given by a list (or tuple) of keys. Like so:

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

Now, ideally I'd like to be able to do this:

>>> d[l]
{1:2, 5:6}

... but that's not working, since it will look for a key matching the tuple (1,5), the same as d[1,5].

d{1,5} isn't even valid Python (as far as I can tell ...), though it might be handy: The curly braces suggest an unordered set or a dictionary, so returning a dictionary containing the specified keys would look very plausible to me.

d[{1,5}] would also make sense ("here's a set of keys, give me the matching items"), and {1, 5} is an unhashable set, so there can't be a key that matches it -- but of course it throws an error, too.

I know I can do this:

>>> dict([(key, value) for key,value in d.iteritems() if key in l])
{1: 2, 5: 6}

or this:

>>> dict([(key, d[key]) for key in l])

which is more compact ... but I feel there must be a "better" way of doing this. Am I missing a more elegant solution?

(I'm using Python 2.7)

Zak
  • 3,063
  • 3
  • 23
  • 30
  • 4
    Ideal solution IMO would be `d[*l]`, but of course that doesn't work. – Ken Williams Jul 08 '20 at 18:02
  • d[1, 5] was and is valid Python. Yes, both in 2.7 and in 3.9 (the 3.x I just tested this in) it worked. A tuple doesn't actually require the parentheses of "(1, 5)". Some places in the syntax do, kind of; for example list.append requires a single tuple argument to be parenthesized, but that was just removing a special-case treatment (list.append assumed a tuple if multiple arguments were given, instead of raising a SyntaxError. – Jürgen A. Erhard Apr 10 '22 at 10:03
  • @JürgenA.Erhard `d[1, 5]` only works if `d` is a list -- not if it's a dictionary. Unless, of course you have an item in your dictionary whose key is `(1, 5)`: with `d` defined as above, you get a key error. But if you define it as `dt = {1: 2, 3: 4, 5: 6, (1, 5): 15}`, it works nicely and produces `15` -- but that's absolutely not what I wanted. The aim was to provide a list/tuple/set of keys and get all matching items. Dictionaries are essentially sets of key/value pairs. Especially since Python 3.9 finally brings a union operator, intersection and exclusion seem like obvious additions. – Zak Apr 28 '22 at 11:19
  • 1
    @Zak you claimed it wasn't *valid Python*. It is perfectly valid Python in that the compiler does not raise a SyntaxError. – Jürgen A. Erhard May 06 '22 at 07:41
  • 1
    @JürgenA.Erhard -- yes, okay, you are right of course. It's just not a valid way to retrieve multiple items from a dictionary. Updated the question to correct this, and added curly braces to the list of things that don't work because I'd really like it if they did) – Zak May 09 '22 at 17:53

13 Answers13

118

On Python 3 you can use the itertools islice to slice the dict.items() iterator

import itertools

d = {1: 2, 3: 4, 5: 6}

dict(itertools.islice(d.items(), 2))

{1: 2, 3: 4}

Note: this solution does not take into account specific keys. It slices by internal ordering of d, which in Python 3.7+ is guaranteed to be insertion-ordered.

jpp
  • 159,742
  • 34
  • 281
  • 339
Cesar Canassa
  • 18,659
  • 11
  • 66
  • 69
  • 2
    So ... that will give me the first two elements, right? Are dictionaries in Python 3 ordered in some way? Because otherwise there's no telling which elements that will return, and it can only work on consecutive ones, when I'd really wanted to have them selected by a list of keys – Zak Dec 07 '17 at 22:18
  • @Zak On python 3.6+ the dictionaries are ordered by default, otherwise you need to use an OrderedDict – Cesar Canassa Dec 09 '17 at 00:02
  • 17
    This answer isn't really related to the question. – Ken Williams Jan 23 '18 at 00:02
  • 1
    +1 for python3 answer, +1 for an answer that works if the original dict is a dict of dicts. Other answes won't work for me if I want to treat a dict of dicts as an ordered-dict of dicts. – weefwefwqg3 Apr 05 '19 at 17:17
  • @CesarCanassacan it is considered bad practice to rely on python 3.6+ dictionaries to be ordered, although it will probably never be changed for backward compatibility. – 0dminnimda Jul 10 '21 at 16:57
  • 1
    For saving your a few seconds: we can also use `itertools.islice(iterable, start, stop[, step])`, for example, `list(islice('ABCDEFG', 2, 4))` returns ['C', 'D']. (from the docs.) – starriet Sep 13 '21 at 03:27
  • Note: this solution is not a solution to the question. – Jürgen A. Erhard Apr 10 '22 at 16:40
65

You should be iterating over the tuple and checking if the key is in the dict not the other way around, if you don't check if the key exists and it is not in the dict you are going to get a key error:

print({k:d[k] for k in l if k in d})

Some timings:

 {k:d[k] for k in set(d).intersection(l)}

In [22]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in l}
   ....: 
100 loops, best of 3: 11.5 ms per loop

In [23]: %%timeit                        
l = xrange(100000)
{k:d[k] for k in set(d).intersection(l)}
   ....: 
10 loops, best of 3: 20.4 ms per loop

In [24]: %%timeit                        
l = xrange(100000)
l = set(l)                              
{key: d[key] for key in d.viewkeys() & l}
   ....: 
10 loops, best of 3: 24.7 ms per

In [25]: %%timeit                        

l = xrange(100000)
{k:d[k] for k in l if k in d}
   ....: 
100 loops, best of 3: 17.9 ms per loop

I don't see how {k:d[k] for k in l} is not readable or elegant and if all elements are in d then it is pretty efficient.

Padraic Cunningham
  • 176,452
  • 29
  • 245
  • 321
  • 5
    Thanks for the timings! ``{k:d[k] for k in l}`` is reasonably readable for someone with some experience (and more so than the slightly more complicated version in my question), but something like ``d.intersect(l)`` would be nicer still: There's a dictionary, a list, and I'm doing something to them, no need to mention k three times, which is neither input nor output of the operation. I know I'm complaining on a very high level :) – Zak Mar 23 '15 at 22:03
  • @Zak, no worries, I think if the the keys are always in the dict and you give maybe more explanatory names to the variables then `{k:d[k] for k in l}` is pretty pythonic – Padraic Cunningham Mar 23 '15 at 22:05
  • 4
    In this case, I think both [Perl](https://stackoverflow.com/a/979276/4521755) and [Ruby](https://stackoverflow.com/a/12941780/4521755) provide more "elegant" solutions. – GLRoman Dec 17 '20 at 00:03
  • 1
    To save a lookup per key in the slice, I iterate over items: `{k:v for k,v in d.items() if k in l}` -- this improved performance for me but yymv – F1Rumors Sep 16 '22 at 17:49
  • @F1Rumors I guess it will depend on the length of the dictionary vs. the length of l. For just a few items, the lookup may be faster, but if you're omitting only a few keys, then iterating over items should be faster. My dictionaries are small enough that I don't usually worry about speed. – Zak Mar 08 '23 at 16:02
59

To slice a dictionary, Convert it to a list of tuples using d.items(), slice the list and create a dictionary out of it.

Here:

d = {1:2, 3:4, 5:6, 7:8}

To get the first 2 items:

first_two = dict(list(d.items())[:2])

first_two:

{1: 2, 3: 4}
Mohammad Kholghi
  • 533
  • 2
  • 7
  • 21
sostom
  • 717
  • 5
  • 3
37

Use a set to intersect on the dict.viewkeys() dictionary view:

l = {1, 5}
{key: d[key] for key in d.viewkeys() & l}

This is Python 2 syntax, in Python 3 use d.keys().

This still uses a loop, but at least the dictionary comprehension is a lot more readable. Using set intersections is very efficient, even if d or l is large.

Demo:

>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = {1, 5}
>>> {key: d[key] for key in d.viewkeys() & l}
{1: 2, 5: 6}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    is this any problem `{key:d[key] for key in l}` – itzMEonTV Mar 23 '15 at 17:52
  • 1
    @itzmeontv: if you know for a fact that all keys in `l` are in `d`, then that's not a problem. But using `d.viewkeys() & l` takes the **intersection**, the keys that are present in both `d` and the set `l`. – Martijn Pieters Mar 23 '15 at 17:53
  • 1
    oh got it :) `{key:d[key] for key in l if key in d}` get weird? – itzMEonTV Mar 23 '15 at 17:56
  • 2
    @itzmeontv: so what if `l` is then large and `d` is small? Just leave this to Python to create the intersection without having to loop yourself. – Martijn Pieters Mar 23 '15 at 17:57
12

Write a dict subclass that accepts a list of keys as an "item" and returns a "slice" of the dictionary:

class SliceableDict(dict):
    default = None
    def __getitem__(self, key):
        if isinstance(key, list):   # use one return statement below
            # uses default value if a key does not exist
            return {k: self.get(k, self.default) for k in key}
            # raises KeyError if a key does not exist
            return {k: self[k] for k in key}
            # omits key if it does not exist
            return {k: self[k] for k in key if k in self}
        return dict.get(self, key)

Usage:

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d[[1, 5]]   # {1: 2, 5: 6}

Or if you want to use a separate method for this type of access, you can use * to accept any number of arguments:

class SliceableDict(dict):
    def slice(self, *keys):
        return {k: self[k] for k in keys}
        # or one of the others from the first example

d = SliceableDict({1:2, 3:4, 5:6, 7:8})
d.slice(1, 5)     # {1: 2, 5: 6}
keys = 1, 5
d.slice(*keys)    # same
kindall
  • 178,883
  • 35
  • 278
  • 309
  • 1
    cool idea, though I'd rather add an additional attribute than mess with existing functions. something like ``d.slice(l)``. Actually, I'd been hoping that something like this exists. More readable than the loops. – Zak Mar 23 '15 at 18:00
  • Sure, it's perfectly cromulent to write another method, or you could use `__call__`. – kindall Mar 23 '15 at 18:25
  • I would not say the first suggestion is cromulent at all. Any other code that uses your `d` will expect `[` to have the usual semantics, but they will have changed. The `slice` example is cromulent. – Ken Williams Jan 23 '18 at 00:05
  • 1
    @KenWilliams Good point. Perhaps a better way would be to pass a list for this. You can't usually use lists as dictionary keys, so you wouldn't lose any functionality. – kindall Jan 23 '18 at 01:22
  • Edited first suggestion to use a list. – kindall Jan 23 '18 at 19:23
  • List as argument to `__getitem__` would be nice… if it wasn't for the fact that current code gives an error to this, so to change these semantics would then make buggy code "work". Wish it had been there from the start, or at least sometime in the early 2.x versions. – Jürgen A. Erhard May 10 '22 at 12:49
6

set intersection and dict comprehension can be used here

# the dictionary
d = {1:2, 3:4, 5:6, 7:8}

# the subset of keys I'm interested in
l = (1,5)

>>>{key:d[key] for key in set(l) & set(d)}
{1: 2, 5: 6}
itzMEonTV
  • 19,851
  • 4
  • 39
  • 49
1

the dictionary

d = {1:2, 3:4, 5:6, 7:8}

the subset of keys I'm interested in

l = (1,5)

answer

{key: d[key] for key in l}
diman Bond
  • 43
  • 2
  • 4
    Hi! This answer is already mentioned in the question. Please elaborate on your answer in order to be more meaningful. – Rishab P Apr 03 '20 at 08:46
  • 1
    what if the keys are in string? And there are 'n' number of pairs? – vijayraj34 Aug 29 '20 at 18:16
  • @vijayraj34: it does not matter which type the keys are, as long as ``l`` is a tuple, list, iterator or similar which contains them. so if ``d={'b':1, 'd':2, 'f':3, 'h':8}``, and you use ``l=('d', 'f'), and the same method works. – Zak Sep 22 '21 at 11:54
1

Another option is to convert the dictionary into a pandas Series object and then locating the specified indexes:

>>> d = {1:2, 3:4, 5:6, 7:8}
>>> l = [1,5]

>>> import pandas as pd
>>> pd.Series(d).loc[l].to_dict()
{1: 2, 5: 6}
Ivan De Paz Centeno
  • 3,595
  • 1
  • 18
  • 20
1

With operator.itemgetter:

dict(zip(l, itemgetter(*l)(d)))

Try it online!

Kelly Bundy
  • 23,480
  • 7
  • 29
  • 65
0

My case is probably relatively uncommon, but, I'm posting it here nonetheless in case it helps someone (though not OP directly).

I came across this question searching how to slice a dictionary that had item counts. Basically I had a dictionary where the keys were letters, and the values were the number of times the letter appeared (i.e. abababc --> {'a': 3, 'b': 3, 'c': 1} I wanted to 'slice' the dictionary so that I could return the most common n keys.

It turns out that this is exactly what a Collections Counter object is for, and instead of needing to 'slice' my dictionary, I could easily just convert it to a collections.Counter and then call most_common(n): https://docs.python.org/3/library/collections.html#collections.Counter.most_common

Raleigh L.
  • 599
  • 2
  • 13
  • 18
0

Slicing a dict using numpy

d = {'a': 1, 'b': 2, 'c': 3, 'd': 4}

dict(np.array([*d.items()])[[1,-2]])   # {'b': '2', 'c': '3'}

Various slice types are supported, see https://numpy.org/doc/stable/user/basics.indexing.html.

0

dict(filter(lambda it: it[0] in l, d.items()))

  • 1
    Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? **If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient.** Can you kindly [edit] your answer to offer an explanation? – Jeremy Caney Aug 18 '23 at 00:05
-1

You can do slicing of the dictionary with the help of the module dictionarify This is the link for documentation-https://github.com/suryavenom/Flexi/blob/main/README.md. Installation - pip install dictionarify

  • 1
    As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 25 '23 at 05:01