1

Suppose a dictionary is expected to have certain keys present. Is there a simple way of adding these specific keys with default values if they are missing?

For example:

default_dict = {'name': '', 'height': 100, 'age': 20} 

d = {'name': 'James', 'age': 65}
d.set_defaults(default_dict)

would update the dictionary d to

{'name': 'James', 'age': 65, 'height': 100}

where the original values of d are kept and only the missing keys are added.

The default_dict should not be destroyed in the process.

Miguel
  • 658
  • 1
  • 6
  • 19

3 Answers3

6

Create a copy of the defaults, and update it with d; if all keys in d are strings, you can do so with one dict() call:

d = dict(default_dict, **d)

For dictionaries with non-string keys, you'd use two steps:

d = default_dict.copy()
d.update({'name': 'James', 'age': 65})

or you could use a loop to update d with any keys not present using dictionary views; this is not as fast however:

d = {'name': 'James', 'age': 65}
d.update((k, default_dict[v]) for k in default_dict.viewkeys() - d)

Replace viewkeys with keys in Python 3.

If you are using Python 3.5 or newer, you can use similar syntax to create a new dictionary:

d = {**default_dict, 'name': 'James', 'age': 65}

The key-value pairs of default_dict are applied first, followed by whatever new keys you set; these will override the old. See PEP 448 - Additional Unpacking Generalizations in the 3.5 What's New documentation.

Any of the methods creating a new dictionary can update an existing dictionary simply by wrapping in a dict.update() call. So the first could update d in-place with:

d.update(dict(default_dict, **d))
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 2
    On 3.5, this can also be done as `{**default_dict, **d}`, with no restriction to string keys. – user2357112 Apr 22 '16 at 17:41
  • @user2357112: yup, was looking up the docs again; this is easiest applied directly to the literal for `d`. – Martijn Pieters Apr 22 '16 at 17:44
  • I'm pretty sure there's a comment somewhere on the internet where Guido calls the `dict(mapping, **othermapping)` "despicable" -- Though perhaps he's loosened his stance on it since then? http://stackoverflow.com/a/26853961/748858 – mgilson Apr 22 '16 at 17:54
  • @mgilson: hey, what can I say, it's a natural extension of the `dict()` function signature and `**kw` expansion in calls. – Martijn Pieters Apr 22 '16 at 17:58
  • @mgilson: and frankly, if it was so despicable, why allow for *basically the same feature* in a dict literal, via PEP 448? One without the 'only strings' limitation even. – Martijn Pieters Apr 22 '16 at 18:00
  • @MartijnPieters -- I'm not sure that I agree. There's nothing (that I know of) that says that the `dict` constructor has to resolve the position mapping before it resolves the keyword arguments. Of course, that's what CPython does (and therefore, probably just about every other implementation), but if it isn't guaranteed explicitly ... – mgilson Apr 22 '16 at 18:00
  • 1
    These solutions create a new dictionary, but how to update the existing one? – Miguel Apr 22 '16 at 18:00
  • @Miguel: `d.update(dict(default_dict, **d))` I guess. Why the insistence? – Martijn Pieters Apr 22 '16 at 18:02
  • @MartijnPieters -- I agree with your second statement "why allow for almost the same feature". Perhaps it is only despicable to the BDFL and he compromised? Perhaps his stance has softened since then ... I will say that the literal version makes more sense to me because it is _guaranteed_ to work from left to right. Mostly I just wanted to point out that this idiom has been contested by at least one important pythonista in the history of the language ;-) – mgilson Apr 22 '16 at 18:02
  • @mgilson: the order *is* guaranteed, actually. The documentation clearly states that the positional argument is used first. – Martijn Pieters Apr 22 '16 at 18:03
  • @MartijnPieters -- Ahh, you're correct. I never noticed that before: [If keyword arguments are given, the keyword arguments and their values are added to the dictionary created from the positional argument.](https://docs.python.org/2/library/stdtypes.html#dict) – mgilson Apr 22 '16 at 18:04
  • @MartijnPieters when you do `f(**kw)` you are unpacking a dictionary into *keyword arguments* to a function, when used like this on the `dict` constructor they never really represent *arguments* but just items in the new dict, hence it being "an abuse of the ** mechanism." However in a dict **literal** `{**kw, **other}` there is no implication of *arguments* to a function. – Tadhg McDonald-Jensen Apr 22 '16 at 18:50
  • @TadhgMcDonald-Jensen: the `dict()` constructor explicitly accepts keyword arguments. Using `**kw` there is no more an abuse than in any other call. – Martijn Pieters Apr 22 '16 at 19:11
4

To update an existing dictionary, I might use the dictionary's setdefault method:

for key, value in dict_of_defaults.items():
    dict_maybe_without_defaults.setdefault(key, value)

If I was creating a new dictionary where I had a small number of keys, I'd probably do something more along the lines of the solution that was posted by Martijn.

mgilson
  • 300,191
  • 65
  • 633
  • 696
2

You may consider using a collections.ChainMap to associate a fallback:

import collections

default_dict = {'name': '', 'height': 100, 'age': 20} 

d = collections.ChainMap({'name': 'James', 'age': 65},default_dict)

>>> d
ChainMap({'name': 'James', 'age': 65}, {'name': '', 'height': 100, 'age': 20})
>>> dict(d) #this flattens the map but isn't necessary for it to work
{'name': 'James', 'height': 100, 'age': 65}

Also note that since all mutating methods will only modify the first mapping default_dict is safe from pop or other methods.

Tadhg McDonald-Jensen
  • 20,699
  • 5
  • 35
  • 59