Welcome, guest | Sign In | My Account | Store | Cart

This recipe allows you to present dictionary based nested data stuctures in your Python code as objects with nested attributes.

It provides dot ('.') based attribute lookups, like this :-

>>> x = d.foo.bar.baz

instead of the usual (and longer) Python dictionary syntax lookups :-

>>> x = d['foo']['bar']['baz']

This recipe saves you lot of typing!

For simplicity and readability this particular version show a read only lookup class.

Python, 54 lines
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#!/usr/bin/env python
import pprint

class DictDotLookup(object):
    """
    Creates objects that behave much like a dictionaries, but allow nested
    key access using object '.' (dot) lookups.
    """
    def __init__(self, d):
        for k in d:
            if isinstance(d[k], dict):
                self.__dict__[k] = DictDotLookup(d[k])
            elif isinstance(d[k], (list, tuple)):
                l = []
                for v in d[k]:
                    if isinstance(v, dict):
                        l.append(DictDotLookup(v))
                    else:
                        l.append(v)
                self.__dict__[k] = l
            else:
                self.__dict__[k] = d[k]

    def __getitem__(self, name):
        if name in self.__dict__:
            return self.__dict__[name]

    def __iter__(self):
        return iter(self.__dict__.keys())

    def __repr__(self):
        return pprint.pformat(self.__dict__)

if __name__ == '__main__':
    cfg_data = eval("""{
        'foo' : {
            'bar' : {
                'tdata' : (
                    {'baz' : 1 },
                    {'baz' : 2 },
                    {'baz' : 3 },
                ),
            },
        },
        'quux' : False,
    }""")

    cfg = DictDotLookup(cfg_data)

    #   Standard nested dictionary lookup.
    print 'normal lookup :', cfg['foo']['bar']['tdata'][0]['baz']

    #   Dot-style nested lookup.
    print 'dot lookup    :', cfg.foo.bar.tdata[0].baz

I like storing configuration data for my scripts in files on disk as simple nested dictionaries and lists or tuples using standard Python syntax. While 'flat is better than nested' some data is best represented in a nested way.

Apart from the inherent security issues associated with loading this type of data from untrusted sources, this way of representing configuration data is easy to create, read, change and diff using your favourite text editor/IDE and is also incredibly quick and simple to dump and load using standard Python calls and modules.

One thing that has bugged me for a good while now is having to type a lot of brackets and quotes once the data nesting gets past 2 to 3 levels deep.

I came up with this fairly simple solution so I didn't have to do so as much typing every time I wanted to access data values.

4 comments

Eric-Olivier LE BIGOT 15 years, 3 months ago  # | flag

Instead of "isinstance(d[k], (list, tuple))", how about using "hasattr(d[k], '__iter__')"? this seems to be more general.

Aaron Gallagher 15 years, 3 months ago  # | flag

@Eric: Doesn't work, as dicts have __iter__ as well.

This is what I use sometimes, which works a lot nicer:

class NestedLookup(object):
    def __init__(self, root):
        self.root = root

    def __call__(self, *lookups):
        obj = self.root
        for key in lookups:
            obj = obj[key]
        return obj

ns = NestedLookup(...)
ns('foo', 'bar', 'tdata', 0, 'baz')

You still have to type the quotes, but that's hardly a problem.

class DictDotLookup(object):
    def __init__(self, obj):
        self.obj = obj

    def __getitem__(self, key):
        return DictDotLookup(self.obj[key])

    def __getattr__(self, key):
        return self[key]

    def get(self, key, default=None):
        try:
            return self[key]
        except (KeyError, IndexError):
            return default

    def __call__(self):
        return self.obj

ddl = DictDotLookup(...)
ddl.foo.bar.tdata[0].baz()

This is also better, because it's a more general-case solution. Using isinstance should usually be avoided at all costs, as it limits code reusability.

denis 15 years, 3 months ago  # | flag

David, your goal "storing configuration data ... using standard Python syntax" is right on. If you like records / structs with named fields, another way of doing this is with namedtuple:

# see http://docs.python.org/library/collections.html#namedtuple
if sys.version_info < (2, 6):
    from namedtuple import namedtuple  # http://code.activestate.com/recipes/500261
else:
    from collections import namedtuple

Fooquux = namedtuple( "Fooquux", "foo quux" )
Bar     = namedtuple( "Bar", "bar" )
Tdata   = namedtuple( "Tdata", "tdata" )
Baz     = namedtuple( "Baz", "baz" )

nmtup = Fooquux(
    foo = Bar(
        Tdata( (Baz(1), Baz(2), Baz(3)) ),
        ),
    quux = False
    )

print "nmtup.foo.bar.tnmtup[0].baz :", nmtup.foo.bar.tdata[0].baz
s = str( nmtup )
print "Fooquux:", s
# Fooquux(foo=Bar(bar=Tdata(tdata=(Baz(baz=1), Baz(baz=2), Baz(baz=3)))),
ev = eval( s )
print "eval:", ev
print "same:", nmtup == ev  # sorted as defined

For one-level DotDicts, there's also this one-liner from http://www.norvig.com/python-iaq.html :

class Rec:
    def __init__(self, **keywordargs): self.__dict__.update(keywordargs)

r = Rec( a=1, b=2 )
r.a += r.b

(It's possible to merge these approaches, but mttiw moretroublethanitsworth).

David Moss (author) 15 years, 2 months ago  # | flag

@Aaron: Very nice - you're Python Fu is strong ;-)

@denis: Interesting alterative. Python 2.6 certainly is chock full of new code and features!

Thanks for sharing.

Created by David Moss on Sun, 14 Dec 2008 (MIT)
Python recipes (4591)
David Moss's recipes (4)

Required Modules

Other Information and Tasks