I'm trying to parse/loop through a deeply nested dictionary/tuple such as this:
#!/usr/bin/env python
movies = {
'in_bruges': {
'display': 'In Bruges',
'actors': {
'Colin Farrel': {
'age': '99',
'height': '160'
},
'Brendan Gleeson': {
'age': '88',
'height': '158'
}
# many more...
}
},
'fargo': {
'display': 'Fargo',
'actors': {
'William H. Macy': {
'age': '109',
'height': '120'
},
'Steve Buscemi': {
'age': '11',
'height': '118'
}
# many more...
}
}
# many more...
}
I can access individual fields just fine:
print(movies['in_bruges']['actors']['Brendan Gleeson']['age']);
88
But I'm having a hard time looping through the whole thing. This is what I have so far...
for movie, val in movies.iteritems():
print movie
for v in val.values():
print v
for x in v.values(): # <-- BREAKS HERE
print x
Error:
AttributeError: 'str' object has no attribute 'values'
I understand why it's breaking... just not sure how to fix it. Also, and probably more importantly, is there a better way to do this? This is a mix of a dictionary and a tuple (correct me if I'm wrong). Should I structure it differently? Or use a different method altogether? I'd rather not read it in from a csv or text...
3 Answers 3
Just add the check of type before access to values/value:
isinstance(v, dict)
or
isinstance(v, basestring)
And the code will be:
for movie, val in movies.iteritems():
print movie
for v in val.values():
if isinstance(v, dict):
for x in v.values():
print x
elif isinstance(v, basestring):
print v
Comments
Not all your values are dictionaries; some are strings:
# ...
'in_bruges': {
'display': 'In Bruges',
'actors': {
# ...
That's a string and a dictionary. You'll have to vary your handling:
for movie, val in movies.iteritems():
print movie
for v in val.values():
if hasattr(v, 'values'):
for x in v.values():
print x
else:
print v
Here I used ducktyping; checking for a method I want to use, rather than a type; if it is present we assume it can be treated as a dictionary. You could also use isinstance(v, dict) but the above is more flexible as it allows anything with the right method to be treated the same.
You could use recursion to handle arbitrary values at any depth:
def print_dictionary(d, indent=0):
try:
for k, v in d.iteritems():
print (indent * ' '), k, '->'
print_dictionary(v, indent + 1)
except AttributeError:
print (indent * ' '), d
Here I used exception handling; if the dict.iteritems() method isn't present an AttributeError is raised. This technique is called Look Before You Leap.
With your demo dictionary this outputs:
>>> print_dictionary(movies)
fargo ->
actors ->
William H. Macy ->
age ->
109
height ->
120
Steve Buscemi ->
age ->
11
height ->
118
display ->
Fargo
in_bruges ->
actors ->
Brendan Gleeson ->
age ->
88
height ->
158
Colin Farrel ->
age ->
99
height ->
160
display ->
In Bruges
1 Comment
# for all movies
for movie, movie_details in movies.iteritems():
print movie
display = movie_details['display']
print display
# for all actors in this movie
for actor,actor_details in movie_details['actors'].iteritems():
print actor
print actor_details['age']
print actor_details['height']