239

I have an empty dictionary. Name: dict_x It is to have keys of which values are lists.

From a separate iteration, I obtain a key (ex: key_123), and an item (a tuple) to place in the list of dict_x's value key_123.

If this key already exists, I want to append this item. If this key does not exist, I want to create it with an empty list and then append to it or just create it with a tuple in it.

In future when again this key comes up, since it exists, I want the value to be appended again.

My code consists of this:

Get key and value.

See if NOT key exists in dict_x.

and if not create it: dict_x[key] == []

Afterwards: dict_x[key].append(value)

Is this the way to do it? Shall I try to use try/except blocks?

Tomerikoo
  • 18,379
  • 16
  • 47
  • 61
Phil
  • 13,875
  • 21
  • 81
  • 126

6 Answers6

387

Use dict.setdefault():

dict.setdefault(key,[]).append(value)

help(dict.setdefault):

    setdefault(...)
        D.setdefault(k[,d]) -> D.get(k,d), also set D[k]=d if k not in D
Nordle
  • 2,915
  • 3
  • 16
  • 34
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • 9
    I used to do this by `dict_x[key] = [some_value] if not dict_x.has_key(key) else dict_x[key] + [some_value]` but this answer suggests a far better way. In fact it gets `set()` as an argument and allows you to use `add()` method... – fatih_dur Feb 09 '16 at 02:27
  • if i do `help(dict.setdefault)` in my python console (v3.9.13) i get a less informative docstring than the one you shared. how come? – gosuto Jun 24 '22 at 10:18
100

Here are the various ways to do this so you can compare how it looks and choose what you like. I've ordered them in a way that I think is most "pythonic", and commented the pros and cons that might not be obvious at first glance:

Using collections.defaultdict:

import collections
dict_x = collections.defaultdict(list)

...

dict_x[key].append(value)

Pros: Probably best performance. Cons: Not available in Python 2.4.x.

Using dict().setdefault():

dict_x = {}

...

dict_x.setdefault(key, []).append(value)

Cons: Inefficient creation of unused list()s.

Using try ... except:

dict_x = {}

...

try:
    values = dict_x[key]
except KeyError:
    values = dict_x[key] = []
values.append(value)

Or:

try:
    dict_x[key].append(value)
except KeyError:
    dict_x[key] = [value]
Jon Clements
  • 138,671
  • 33
  • 247
  • 280
antak
  • 19,481
  • 9
  • 72
  • 80
  • Hello, why do you think .setdefault creates unnecessary dictionaries? – Phil Oct 16 '12 at 21:07
  • 2
    I don't think `.setdefault()` creates unnecessary dictionaries. I think *I'm* creating unnecessary `list`s (i.e. `[]`) in the second argument of `.setdefault()` that's never used if `key` already exists. I could use `dict.setdefault()` (for the benefit of efficient key hashing), and use a variable to reuse unused `list`s but that adds a few more lines of code. – antak Oct 17 '12 at 02:59
  • 1
    IIRC, in Python an empty list in an equality is considered a constant at the bytecode level, but this needs some confirmation by a bytecode guru (or just use the disas module). – gaborous Jul 16 '15 at 00:36
  • 2
    Using `.setdefault` creates a regular `dict` where absent key look-up will result in a `KeyError` while `collections.defaultdict(list)` creates a `dict` where absent key lookups will insert an empty `list`-- i think you should choose based on which behaviour you want – Chris_Rands Oct 30 '19 at 14:10
  • I tried something similar to collections.defaultdict in my own code and it had unexpected side effects. For example consider the following IDLE exchange: >>> list_dict = defaultdict(list) >>> len(list_dict) 0 >>> len(list_dict[0]) 0 >>> len(list_dict) 1 It appears that when Python invokes the default value it adds the key to the dictionary without you actively setting it, which is going to create a lot of empty lists if the default is used much. I'm going to roll my own wrapper functions for the dictionary, inefficient but hopefully more predictable. – RDBury Mar 01 '20 at 10:17
39

You can use a defaultdict for this.

from collections import defaultdict
d = defaultdict(list)
d['key'].append('mykey')

This is slightly more efficient than setdefault since you don't end up creating new lists that you don't end up using. Every call to setdefault is going to create a new list, even if the item already exists in the dictionary.

Tahlor
  • 1,642
  • 1
  • 17
  • 21
Nathan Villaescusa
  • 17,331
  • 4
  • 53
  • 56
16

You can use defaultdict in collections.

An example from doc:

s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
d = defaultdict(list)
for k, v in s:
    d[k].append(v)
iMom0
  • 12,493
  • 3
  • 49
  • 61
11
dictionary['key'] = dictionary.get('key', []) + list_to_append
  • 1
    You should explain the (very minor) advantage that this has in the presence of certain exceptions; as just code, it's not clear why the additional answer is needed. – Davis Herring Jul 03 '20 at 03:36
  • 1
    Hi, just an alternative without additional imports. This can clearly be done with an if statement. I am just suggesting an alternative using the power of .get() instead of using dict[]. – Tomas Silva Ebensperger Jul 08 '20 at 17:10
  • The top two answers mention two different ways with no imports (albeit not `dict.get`). – Davis Herring Jul 08 '20 at 18:36
3

TL;DR

The more lengthy and expressive approach seems also to be more performant.

if key in dest:
    dest[key].append(value)
else:
    dest[key] = [value]

Longer answer

I wrote some python lines to check whether the proposed approach is actually the best in term of performance.

d1 = {}
d2 = {}

def add1(key, value, dest):
        dest.setdefault(key, []).append(value)
            
def add2(key, value, dest):
    if key in dest:
        dest[key].append(value)
    else:
        dest[key] = [value]

This results in

%timeit add1('a', 1.1, d1)
  96.2 ns ± 0.0972 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)
%timeit add2('a', 1.1, d2)
  89.1 ns ± 0.111 ns per loop (mean ± std. dev. of 7 runs, 10,000,000 loops each)

Tested on my python setup (3.10.4 (main, Apr 2 2022, 09:04:19) [GCC 11.2.0]) executing Jupyter NB.

It's worth it to point out that opting for the lengthier and more expressive approach can also impact positively on performance in this case.

Franc
  • 102
  • 1
  • 4