3

I'm a Python 3.6 user. I'm facing an apparent simple task: convert a list to a dict. I saw this helpful question for taking inspiration.

Let me explain my goal: starting from a list of objects like this:

class AObject():
def __init__(self, sp, sb, n):
    self.superset = sp
    self.subset   = sb
    self.number   = n

I want to have a dictionary of this type: d[superset][subset] = number.

My starting point is a simple list:

s = set((('a','a1'),('a','a2'),('b','b1'),('b','b2'))) #set of tuples
o = [AObject(t[0], t[1], n) for t, n in zip(s, range(0,4))]

whose length is 4.

Now I create the dictionary in this way:

d = {x.superset: {x.subset : x.number} for x in o}

but

d
Out[5]: {'a': {'a1': 1}, 'b': {'b1': 3}}

Where did the other two dictionary items go??

Same result using:

d1 = dict(map(lambda x: (x.superset, {x.subset : x.number}), o))

Meanwhile with a for loop:

from collections import defaultdict
d2 = defaultdict(dict)
for x in o:
    d2[x.superset][x.subset] = x.number

d2
defaultdict(dict, {'a': {'a1': 1, 'a2': 0}, 'b': {'b1': 3, 'b2': 2}})

My questions:

  • I think in same way is happening an update of the dictionary when I use dict comprehension keeping just one element for each superset as explained here. Am I right?

  • How can I build my nested dictionary in a pythonic way?

Marco
  • 367
  • 1
  • 10

1 Answers1

3

The problem you have is here:

d = {x.superset: {x.subset : x.number} for x in o}

anytime you get a new x.superset that is already in the dict to be built, it overrides the former one - its similar to

d2 = { k:v for k,v in [ (1,4),(1,7),(1,9)]} # d2 == {1:9,} - last value to key 1 survives

The fact that you box one layer more doesn't matter - if you provide multiple same keys in the dict-comp you get overwriting behaviour - not update-behaviour.

Your

from collections import defaultdict
d2 = defaultdict(dict)
for x in o:
    d2[x.superset][x.subset] = x.number

is as pythonic as it gets.


The defaultdict-aproach is similar (but more effective) to:

d2 = {}
for x in o:
    k = d2.setdefault(x.superset,{})
    k[x.subset] = x.number

# {'a': {'a1': 1, 'a2': 0}, 'b': {'b1': 3, 'b2': 2}}

The dict comp approach is similar to:

for x in o:
    d2[x.superset] = {x.subset:x.number}

# {'a': {'a1': 1}, 'b': {'b1': 3}}
Patrick Artner
  • 50,409
  • 9
  • 43
  • 69
  • What is the difference underneath `for` and dict-comp? I mean: why the latter does override? I thought dict-comp was a compact way to replicate the explicit `for` loop but I must missing something... – Marco Nov 02 '18 at 17:35
  • 1
    @Marco the difference is using the defaultdict - list comp uses "normal" dicts and the defaultdict handles _if this key already exists in the dict then add to the value of the deisgnated key using whatever whas designated as defaultelement else create an empty defaultelement and use that to add to_ – Patrick Artner Nov 02 '18 at 17:38