views:

1203

answers:

6

A lot of times in Perl, I'll do something like this:

$myhash{foo}{bar}{baz} = 1

How would I translate this to Python? So far I have:

if not 'foo' in myhash:
    myhash['foo'] = {}
if not 'bar' in myhash['foo']:
    myhash['foo']['bar'] = {}
myhash['foo']['bar']['baz'] = 1

Is there a better way?

+3  A: 

I guess the literal translation would be:

 mydict = {'foo' : { 'bar' : { 'baz':1}}}

Calling:

 >>> mydict['foo']['bar']['baz']

gives you 1.

That looks a little gross to me, though.

(I'm no perl guy, though, so I'm guessing at what your perl does)

Dana
That only works at initialization time, though, right?
mike
I'm not sure what you mean.
Dana
+2  A: 

Nested dictionaries like that are (often) called a poor mans objects. Yes, there is an implication and it might correlate with pythons object oriented nature.

Tetha
+14  A: 
class AutoVivification(dict):
    """Implementation of perl's autovivification feature."""
    def __getitem__(self, item):
        try:
            return dict.__getitem__(self, item)
        except KeyError:
            value = self[item] = type(self)()
            return value

Testing:

a = AutoVivification()

a[1][2][3] = 4
a[1][3][3] = 5
a[1][2]['test'] = 6

print a

Output:

{1: {2: {'test': 6, 3: 4}, 3: {3: 5}}}
nosklo
Is it possible to extend it so it supports the following behavior: a[1][2][3] += some_value. So if the key did not exist in advance, then the a[1][2][3] would be initialized with the default value of the type(some_value)?
mezhaka
+5  A: 

Is there a reason it needs to be a dict of dicts? If there's no compelling reason for that particular structure, you could simply index the dict with a tuple:

mydict = {('foo', 'bar', 'baz'):1} # Initializes dict with a key/value pair
mydict[('foo', 'bar', 'baz')]      # Returns 1

mydict[('foo', 'unbar')] = 2       # Sets a value for a new key

The parentheses are required if you initialize the dict with a tuple key, but you can omit them when setting/getting values using []:

mydict = {}                        # Initialized the dict
mydict['foo', 'bar', 'baz'] = 1    # Sets a value
mydict['foo', 'bar', 'baz']        # Returns 1
zweiterlinde
Can you be clear on when you can omit the parentheses? Is it because the comma is the tuple operator, and the parentheses only needed if we have ambiguous grouping?
Kiv
Added clarification, thx.
zweiterlinde
A: 

Wasn't this question just asked? See What is the best way to implement nested dictionaries in Python?, specifically my answer ;-)

Pete
+7  A: 

If the amount of nesting you need is fixed, collections.defaultdict is wonderful.

e.g. nesting two deep:

myhash = collections.defaultdict(dict)
myhash[1][2] = 3
myhash[1][3] = 13
myhash[2][4] = 9

If you want to go another level of nesting, you'll need to do something like:

myhash = collections.defaultdict(lambda : collections.defaultdict(dict))
myhash[1][2][3] = 4
myhash[1][3][3] = 5
myhash[1][2]['test'] = 6

edit: MizardX points out that we can get full genericity with a simple function:

def makehash():
    return collections.defaultdict(makehash)

Now we can do:

myhash = makehash()
myhash[1][2] = 4
myhash[1][3] = 8
myhash[2][5][8] = 17
# etc
John Fouhy
or def makehash(): return collections.defaultdict(makehash); myhash = makehash()
MizardX
I've got no problems with "traditional" recursive functions, but there's something about that that I find unintuitive. Odd. Anyway, thanks!
John Fouhy
Thanks for this. That lambda: defaultdict() is what I needed.
wheaties
Unfortunately that recursive makehash approach does not handle the following a = makehash(); a['foo'] += 1; as I understand it's because the default_factory is not specified here. Do you know how to overcome this issue?
mezhaka