0

I have a class called nesteddict derived from collections.defaultdict that hold a nested set of dictionaries:

import collections

class nesteddict(collections.defaultdict):
    """Nested dictionary structure.

    Based on Stack Overflow question 635483
    """
    def __init__(self):
        collections.defaultdict.__init__(self, nesteddict)
        self.locked = False

I would like to be able to perform an operation an instance that converts all of the nesteddict objects to python-native dict objects.

One way to do this is to have a method:

def todict(self):
    for (key,val) in self.iteritems():
        if isinstance(val,nesteddict):
            val.todict()
            self[key] = dict(val)
    self = dict(self)

This is successful at replacing all the inner mapping objects with dict types, but the last statement in the method is obviously not going to work.

Here is an example:

In [93]: a = pyutils.nesteddict()

In [94]: a[1][1] = 'a'

In [95]: a[1][2] = 'b'

In [96]: a[2][1] = 'c'

In [97]: a[2][2] = 'd'

In [98]: print a
defaultdict(<class 'pyutils.nesteddict'>, {1: defaultdict(<class 'pyutils.nesteddict'>, {1: 'a', 2: 'b'}), 2: defaultdict(<class 'pyutils.nesteddict'>, {1: 'c', 2: 'd'})})

In [99]: a.todict()

In [100]: print a
defaultdict(<class 'pyutils.nesteddict'>, {1: {1: 'a', 2: 'b'}, 2: {1: 'c', 2: 'd'}})

Is there a way to do this in python? Have a method that converts its object to another type? If not, what is a good alternative to this. Note that the datatype in practice may be large, so it would be preferable not to just make a copy and then return it.

Thanks!
Uri

Uri Laserson
  • 2,391
  • 5
  • 30
  • 39
  • 3
    "I would like to be able to perform an operation an instance that converts all of the nesteddict objects to python-native dict objects. " Why? It already is a `dict` object. You don't need to convert anything. It's a subclass of `defaultdict` which means it **is** a `dict`. – S.Lott Dec 14 '10 at 18:53
  • After loading data into it, I want to "lock" it. One way to implement this is so make the default_factory method a function that raises a KeyError, which seems to work fine. However, I was having trouble serializing this class using cPickle. Conversion to a pure dict will allow easy serialization (including with json), and will effectively lock the dictionary in a similar fashion. – Uri Laserson Dec 16 '10 at 20:03
  • @S.Lott pickle is a common reason to want a conversion to pure dictionaries: otherwise, to unpickle it, you need to bring the definition of the specialized dict, eg. `infinite_defaultdict = lambda: defaultdict(infinite_defaultdict)`. – Quentin Pradet Sep 06 '13 at 13:24

4 Answers4

5

Do it as a free function, and while you're at it, consider a more functional style approach:

def undefaulted(x):
  return dict(
    (k, undefaulted(v))
    for (k, v) in x.iteritems()
  ) if isinstance(x, nesteddict) else x

a = undefaulted(a)
Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
  • For future visitors: the other answer using a dict comprehension is indeed superior to using `dict()`. In addition to @Jamie's claim, it's also faster *and* more idiomatic. – Karl Knechtel Jan 29 '15 at 13:08
3

I've just faced this problem myself using nested defaultdicts. My solution:

def dictify(d):
    return {k:dictify(v) for k,v in d.items()} if \
        isinstance(d,nesteddict) else d

It is better to use {} rather than dict() because calling the constructor of dict() limits the number of keyword arguments to 255. See: What is a maximum number of arguments in a Python function?

Community
  • 1
  • 1
Jamie
  • 31
  • 1
1

dict(a) will give you a default dict from any object that derives from defaultdict. That is, assuming you havn't overridden the required special methods.

dietbuddha
  • 8,556
  • 1
  • 30
  • 34
0

The first thing I notice in your code, is that you're modifying (or trying to) the self variable. That variable is just pointing at the current instance of your class. If you reassign it, you're just pointing it at another value, but the previous value pointed by self remains unchanged. This is just how Python works.

So, what you should to is just return the result of the transformation in your method... something like this:

def anothertodict(self):
    stuff = dict(self)
    for (key,val) in stuff.iteritems():
        if isinstance(val,nesteddict):
            stuff[key] = val.anothertodict()
    return stuff

That way, if you do a: print a.anothertodict() you'll get what you're expecting to get.

PS: Why do you need to convert from defaultdict to dict?

Federico Cáceres
  • 1,216
  • 12
  • 19