140

How can I convert a defaultdict

number_to_letter
defaultdict(<class 'list'>, {'2': ['a'], '3': ['b'], '1': ['b', 'a']})

to be a common dict?

{'2': ['a'], '3': ['b'], '1': ['b', 'a']}
martineau
  • 119,623
  • 25
  • 170
  • 301
user2988464
  • 3,251
  • 6
  • 21
  • 25
  • 21
    `dict(number_to_letter)` – Tim Peters Dec 06 '13 at 16:19
  • 4
    @TimPeters since this is actually a nicely written question, with an obvious title (and should show up easily in searches), for future reference it'd be worth putting that as an answer if we can't find a dupe that's better... – Jon Clements Dec 06 '13 at 16:20
  • This answer should help: http://stackoverflow.com/a/3031915/1628832 – karthikr Dec 06 '13 at 16:20
  • 1
    @JonClements, good point, but DSM already did - I hope his answer is accepted :-) – Tim Peters Dec 06 '13 at 16:22
  • 2
    @TimPeters congrats on the 10k btw... I had a feeling it wouldn't take you long - you should (shameless plug) pop by http://chat.stackoverflow.com/rooms/6/python at some point :) – Jon Clements Dec 06 '13 at 16:24
  • Just something to add here: I had nested default dicts and unfortunately, `dict(my_dict)` only takes the top-level default dict and converts it. I solved this case via: `json.loads(json.dumps(my_dict))` – Christian Weiss Oct 30 '22 at 13:09

3 Answers3

177

You can simply call dict:

>>> a
defaultdict(<type 'list'>, {'1': ['b', 'a'], '3': ['b'], '2': ['a']})
>>> dict(a)
{'1': ['b', 'a'], '3': ['b'], '2': ['a']}

but remember that a defaultdict is a dict:

>>> isinstance(a, dict)
True

just with slightly different behaviour, in that when you try access a key which is missing -- which would ordinarily raise a KeyError -- the default_factory is called instead:

>>> a.default_factory
<type 'list'>

That's what you see when you print a before the data side of the dictionary appears.

So another trick to get more dictlike behaviour back without actually making a new object is to reset default_factory:

>>> a.default_factory = None
>>> a[4].append(10)
Traceback (most recent call last):
  File "<ipython-input-6-0721ca19bee1>", line 1, in <module>
    a[4].append(10)
KeyError: 4

but most of the time this isn't worth the trouble.

DSM
  • 342,061
  • 65
  • 592
  • 494
  • i just start to learn programming with Python. Can you explain more about how to use `default_factory`? I just have no clue on what's going on. Sorry. – user2988464 Dec 06 '13 at 16:29
  • And what's 'KeyError'? – user2988464 Dec 06 '13 at 16:29
  • 6
    @user2988464: you've already used `default_factory` -- that's the function that you passed when you made the `defaultdict` in the first place, like `defaultdict(int)` or `defaultdict(list)`. Whenever you try to look up a key in the dictionary, like `a['3']`, for example, in a usual dictionary you either get the value or it raises a `KeyError` exception (like an error, telling you it couldn't find that key). But in a `defaultdict`, it instead calls the `default_factory` function to *make* a new default value (that's the "default" in `defaultdict`) for you. – DSM Dec 06 '13 at 16:37
  • 3
    Helpful to note that Django's .items template property won't work with defaultdict, which is why I ended up here! So there's one good reason for the conversion – Ben Jul 18 '14 at 19:07
  • 2
    Also helpful to note that `pickle` (for saving python objects to a file) can't handle `defaultdict`s in [many circumstances](http://stackoverflow.com/a/16439720/420867). – drevicko Sep 19 '14 at 03:38
  • A useful "detail" is that `dict(…)` creates a _copy_ of the original dictionary. This may not be desirable, if the code must be optimized. – Eric O. Lebigot Aug 13 '16 at 20:18
  • Another reason to convert is that `yaml.safe_dump` doesn't handle defaultdicts. :-( – GaryO Apr 09 '20 at 01:14
  • Does not work if your defaultdict is recursively defined. See other solutions of it is the case (I like the one using the json library – Skratt Sep 04 '22 at 13:57
  • yes,it works no matter how much recursively it is structured – focus zheng Feb 17 '23 at 06:38
36

If your defaultdict is recursively defined, for example:

from collections import defaultdict
recurddict = lambda: defaultdict(recurddict)
data = recurddict()
data["hello"] = "world"
data["good"]["day"] = True

yet another simple way to convert defaultdict back to dict is to use json module

import json
data = json.loads(json.dumps(data))

and of course, the values contented in your defaultdict need to be confined to json supported data types, but it shouldn't be a problem if you don't intent to store classes or functions in the dict.

Meow
  • 4,341
  • 1
  • 18
  • 17
  • 9
    Good idea, too bad my data won't serialize to JSON :*( – Kasapo Apr 28 '16 at 20:47
  • 1
    that's a really cool definition of `recurddict`! i didn't think of that. i came across [this one](https://www.reddit.com/r/Python/comments/rq6sv/comment/c47xbv1), but yours is also pretty neat. it's almost exactly the theoretical construction i was thinking of when i first wanted such an infinite dict: `x = defaultdict(lambda: defaultdict(lambda: defaultdict(lambda: ...)))` – starwarswii Sep 08 '22 at 22:42
21

If you even want a recursive version for converting a recursive defaultdict to a dict you can try the following:

#! /usr/bin/env python3

from collections import defaultdict

def ddict():
    return defaultdict(ddict)

def ddict2dict(d):
    for k, v in d.items():
        if isinstance(v, dict):
            d[k] = ddict2dict(v)
    return dict(d)

myddict = ddict()

myddict["a"]["b"]["c"] = "value"

print(myddict)

mydict = ddict2dict(myddict)

print(mydict)
Asclepius
  • 57,944
  • 17
  • 167
  • 143
white_gecko
  • 4,808
  • 4
  • 55
  • 76
  • That's exactly what I needed! Unfortunately, my defaultdict has some keys that aren't JSON serializable without a custom encoder (the keys are tuples -- not my choice, just my problem) – Kasapo Apr 28 '16 at 20:46
  • 2
    Good one. Using a dictionary comprehension is succinct if it's only two layers deep (i.e. `defaultdict(lambda: defaultdict(int))`): `{k: dict(v) for k, v in foo.items()}` – ggorlen Oct 31 '19 at 01:19