Printing from [Awkward] Python Nested Dictionaries

Question:

So, I’m learning Python (3.x) and it makes a lot of sense until I came across this very awkward progression of dictionaries and trying to get a better understanding of what it is doing. I’m hoping someone could explain in [simple] terms why it does/doesn’t work.

I’m fully aware that there may be better ways of doing what I am trying to do – so I’m open to seeing and learning those too!

I need to extract/print ‘this_entry’ in these examples.

dict1 = {'simple':'this_entry'}
dict2 = {'key1':{'key2':'this_entry'}}
dict3 = {'key1':[{'nest_key':['this is harder',['this_entry']]}]}

The first two work absolutely fine using

print (dict1['simple'])
print (dict2['key1']['key2'])

BUT the third one is (obviously designed to be) much more tricky, if I use

print (dict3['key1']['nest_key'][1])

It doesn’t work and throws up errors…

So to debug I started with the base level to see what works/what doesn’t.

print (dict3['key1'])                #shows [{'nest_key': ['this is harder', ['hello']]}]
print (dict3['key1']['nest_key'][1])    #breaks here showing: 
                                     #TypeError: list indices must be integers or slices, not str

Why does it break there? If I add in a ‘pointer’ for the key (is that even the right word?) it kinda works, I just want to know why it breaks if that isn’t used and why the ‘pointer’ is needed in this instance (other than ‘it just doesn’t work without it’).

print (dict3['key1'][0]['nest_key'][1])    #works-ish but shows entry ['hello'], not hello

There’s something obviously missing too as the output is incorrect. I’m guessing understaning why I need the first [0] will also explain on how to extract the entry I desire properly?

Thanks in advance!

Asked By: Dan Lewis

||

Answers:

With your given dict3 = {'key1':[{'nest_key':['this is harder',['this_entry']]}]}, consider running the following statements, and the output they give:

>>> print(dict3["key1"]) # access the value of 'key1', the value is a list
[{'nest_key': ['this is harder', ['this_entry']]}]

>>> print(dict3["key1"][0]) # access the 1st elemtn of the list which is a dict
{'nest_key': ['this is harder', ['this_entry']]}

>>> print(dict3["key1"][0]["nest_key"]) # access the value of 'nest_key, the value is a list
['this is harder', ['this_entry']]

>>> print(dict3["key1"][0]["nest_key"][1]) # get 2nd element of the list, which is another list
['this_entry']

>>> print(dict3["key1"][0]["nest_key"][1][0]) # finaly get the single item in the list, value is a string
this_entry
Answered By: jjislam

TL;DR

  • dict3['key1'] -> returns a list
    • i.e. [{'nest_key': ['this is harder']}]
  • dict3['key1'][0] -> returns first item in the list
    • i.e. {'nest_key': ['this is harder']}
  • dict3['key1'][0]['nest_key'] -> returns the nest_key value in the inner dict
    • i.e. ['this is harder']
  • dict3['key1'][0]['nest_key'][0] -> returns the first item in the inner dict
    • i.e. 'this is harder'
  • dict3['key1'][0]['nest_key'][1] -> returns the 2nd item in the inner dict
    • i.e. ['this_entry']
  • dict3['key1'][0]['nest_key'][1][0] -> returns the 1st string in the 2nd item in the inner dict
    • i.e. 'this_entry'
  • dict3['key1'][0]['nest_key'][1][0][0] -> returns the 1st character of the 1st string in the 2nd item in the inner dict
    • i.e. 't'

In Long

Firstly, collections.defaultdict is a good friend. https://docs.python.org/3/library/collections.html#collections.defaultdict

An example usage:

from collections import defaultdict

x = defaultdict(list)
x['key1'].append('abc')

print(x)

[out]:

defaultdict(<class 'list'>, {'key1': ['abc']})

Next if we kind of initialize the "types" of your dict3 object, it will look something like this:

In human words, dictionary of list of dictionary of list of strings or list of strings, i.e. "messy"/"awkward", see also

In Python types:

from collections import defaultdict

x = defaultdict(lambda: [defaultdict] )

x['key1'].append({'abc': 'foo bar'})
print(x)

[out]:

defaultdict(<function <lambda> at 0x7f75cebca0d0>, 
{'key1': [<class 'collections.defaultdict'>, {'abc': 'foo bar'}]})

If we look at first how you can/should initialize dict3 type, you would do something like:

from collections import defaultdict

x = defaultdict(lambda: list(defaultdict(lambda: list)) )

x['key1'].append({'nest_key': ['this is harder']})
print(x)

[out]:

defaultdict(<function <lambda> at 0x7f75cee11940>, 
 {'key1': [defaultdict(<function <lambda>.<locals>.<lambda> at 0x7f75cf38c4c0>, {}), 
   {'nest_key': ['this is harder']}]})

And then to access the 'this is harder' string you would do:

  • x['key1'] -> returns a list
    • i.e. [{'nest_key': ['this is harder']}]
  • x['key1'][0] -> returns first item in the list
    • i.e. {'nest_key': ['this is harder']}
  • x['key1'][0]['nest_key'] -> returns the nest_key value in the inner dict
    • i.e. ['this is harder']
  • x['key1'][0]['nest_key'][0] -> returns the first item in the inner dict
    • i.e. 'this is harder'

But what happens if I want dict3 = {'key1':[{'nest_key':['this is harder',['this_entry']]}]}, where the penultimate inner most list contains a list or string?

To initialize the values, you would have done something like

from collections import defaultdict

x = defaultdict(lambda: list(defaultdict(lambda: list)) )

x['key1'].append({'nest_key': ['this is harder']})

x['key1'][0]['nest_key'].append('this is inserting a string')
x['key1'][0]['nest_key'].append(['this is inserting a list'])


print(type(x['key1'][0]['nest_key'][0])) # "this is harder" -> str
print(type(x['key1'][0]['nest_key'][1])) # "this is inserting a string" -> str
print(type(x['key1'][0]['nest_key'][2])) # ['this is inserting a list'] -> list

[out]:

<class 'str'>
<class 'str'>
<class 'list'>

To summarize the initialization:

Human words Initialization Usage Outputs
dictionary of list x = defaultdict(list) x[‘key1’].append(‘abc’) {‘key1’: [‘abc’]}
dictionary of list of dict x = defaultdict(lambda: list(defaultdict)) x[‘key1’].append({‘key2′:’abc’})` {‘key1’: [{‘key2′:’abc’}]}
dictionary of list of dict of list x = defaultdict(lambda: list(defaultdict(lambda: list)) ) x[‘key1’].append({‘key2’: [‘foo bar’]}) defaultdict(<function at 0x7f75cebbcc10>, {‘key1’: [defaultdict(<function .. at 0x7f75cebbc940>, {}), {‘key2’: [‘foo bar’]}]})

Q: Why do I bother with how the object is initialize if I just to want access an item inside?

A: If you don’t know how the object is initialize, you most probably can’t modify / remove / add items. And also knowing how it’s initialized also helps you understand how to access the items inside.

Food for thought

If access/parsing to the item you want is kinda complicated, is there an easier way to store your data? I.e. is there a better data structure?

Answered By: alvas

dict3[‘key1’] = [{‘nest_key’: [‘this is harder’, [‘hello’]]}]
#answers with a dictionary in a array

dict3[‘key1’][0] = {‘nest_key’: [‘this is harder’, [‘hello’]]}
#answers with just a dictionary (nest_key : 2D array)

dict3[‘key1’][0][‘nest_key’] = [‘this is harder’, [‘hello’]]
#answers with 2D array

dict3[‘key1’][0][‘nest_key’][0] = ‘this is harder’, [‘hello’]

dict3[‘key1’][0][‘nest_key’][0][1] = ‘hello’

Answered By: Jonathan

Thanks to all for the answers, I have a much better understanding now and understand the terminology better too. I think this sums up what I was trying to learn:

dict3 = {'key1':[{'nest_key':['this is harder',['this_entry']]}]}
# to extract/grab/print 'this_entry'
print (dict3['key1'][0]['nest_key'][1][0])

The first part [‘key1’][0] is required as it’s a list with just a single element – and this is what we/I want to access. Next, to look at [‘nest_key’] index 1, thus the [1], and here it has again a single element thus requiring the final index of [0].

All makes much more sense and understood where I was going wrong.

Answered By: Dan Lewis
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.