Accessing values nested within dictionaries - python

I have a dictionary which contains dictionaries, which may also contain dictionaries, e.g.
dictionary = {'ID': 0001, 'Name': 'made up name', 'Transactions':
{'Transaction Ref': 'a1', 'Transaction Details':
{'Bill To': 'abc', 'Ship To': 'def', 'Product': 'Widget A'
...} ...} ... }
Currently I'm unpacking to get the 'Bill To' for ID 001, 'Transaction Ref' a1 as follows:
if dictionary['ID'] == 001:
transactions = dictionary['Transactions']
if transactions['Transaction Ref'] == 'a1':
transaction_details = transactions['Transaction Details']
bill_to = transaction_details['Bill To']
I can't help but think this is is a little clunky, especially the last two lines - I feel like something along the lines of the following should work:
bill_to = transactions['Transaction Details']['Bill To']
Is there a simpler approach for drilling down into nested dictionaries without having to unpack into interim variables?

You can use something like this:
>>> def lookup(dic, key, *keys):
... if keys:
... return lookup(dic.get(key, {}), *keys)
... return dic.get(key)
...
>>> d = {'a':{'b':{'c':5}}}
>>> print lookup(d, 'a', 'b', 'c')
5
>>> print lookup(d, 'a', 'c')
None
Additionally, if you don't want to define your search keys as individual parameters, you can just pass them in as a list like this:
>>> print lookup(d, *['a', 'b', 'c'])
5
>>> print lookup(d, *['a', 'c'])
None

bill_to = transactions['Transaction Details']['Bill To']
actually works. transactions['Transaction Details'] is an expression denoting a dict, so you can do lookup in it. For practical programs, I would prefer an OO approach to nested dicts, though. collections.namedtuple is particularly useful for quickly setting up a bunch of classes that only contain data (and no behavior of their own).
There's one caveat: in some settings, you might want to catch KeyError when doing lookups, and in this setting, that works too, it's hard to tell which dictionary lookup failed:
try:
bill_to = transactions['Transaction Details']['Bill To']
except KeyError:
# which of the two lookups failed?
# we don't know unless we inspect the exception;
# but it's easier to do the lookup and error handling in two steps

Following is another way of accessing nested dictionaries
>>> dbo={'m':{'d':{'v':{'version':1}}}}
>>> name='m__d__v__version' # it'll refer to 'dbo['m']['d']['v']['version']', '__' is the separator
>>> version = reduce(dict.get, name.split('__'), dbo)
>>> print version
1
>>>
Here, variable 'name' refers to 'dbo['m']['d']['v']['version']', which seems much shorter and neat.
This method will not throw KeyError. If a key is not found then you'll get 'None'.
Ref.: http://code.activestate.com/recipes/475156-using-reduce-to-access-deeply-nested-dictionaries/

You can access nested dictionaries with a tuple using NestedDict.
>>> from ndicts.ndicts import NestedDict
>>> nested_dict = {"a": {"a": 0, "b": 1},
... "b": {"a": 2, "b": 3}}
>>> nd = NestedDict(nested_dict)
>>> nd["a", "a"]
0
>>> nd["b", "a"]
2
>>> nd["a"]
{"a": 0, "b": 1}
To install ndicts:
pip install ndicts

Related

Python sort list to set

*edit
I make
word=['I','love','hello','world','love','I']
when I convert to set, It change the order to
print(set(word))
output: {'world', 'I', 'hello', 'love'}
How to sort the set again to be
{'I', 'love', 'hello', 'world'}
Sets are unordered. If you want order, convert back to a list.
E.g.
print(sorted(set(word)))
sorted will sort your items and return a list.
However, if you want to retain the order of your elements rather than sort them, you can use a set for deduplication and a list for ordering, something like this:
def unique(items):
seen = set()
result = []
for item in items:
if item not in seen:
seen.add(item)
result.append(item)
return result
and use it as:
>>> word = ['I','love','hello','world','love','I']
>>> print(unique(word))
['I', 'love', 'hello', 'world']
If you just want an ordered collection of unique values, you can create a dict from the list, either with a dict comprehension or dict.fromkeys. In Python 3, dictionaries will retain insertion order; for older versions, use collections.OrderedDict. The dict will have values besides the keys, but you can just ignore those.
>>> word = ['a','b','c','c','b','e']
>>> {k: None for k in word}
{'a': None, 'b': None, 'c': None, 'e': None}
>>> dict.fromkeys(word)
{'a': None, 'b': None, 'c': None, 'e': None}
Other than sorted, this also works if the original order is different than the sorted order.
>>> word = ['f','a','b','c','c','b','e']
>>> dict.fromkeys(word)
{'f': None, 'a': None, 'b': None, 'c': None, 'e': None}
You can then either convert the result to list or keep it a dict and add more values, but if you make it a set, the order will be lost again. Like a set, the dict also allows fast O(1) lookup, but no set operations like intersection or union.

list comprehension using dictionary entries

trying to figure out how I might be able to use list comprehension for the following:
I have a dictionary:
dict = {}
dict ['one'] = {"tag":"A"}
dict ['two'] = {"tag":"B"}
dict ['three'] = {"tag":"C"}
and I would like to create a list (let's call it "list") which is populated by each of the "tag" values of each key, i.e.
['A', 'B', 'C']
is there an efficient way to do this using list comprehension? i was thinking something like:
list = [x for x in dict[x]["tag"]]
but obviously this doesn't quite work. any help appreciated!
This is an extra step but gets the desired output and avoids using reserved words:
d = {}
d['one'] = {"tag":"A"}
d['two'] = {"tag":"B"}
d['three'] = {"tag":"C"}
new_list = []
for k in ('one', 'two', 'three'):
new_list += [x for x in d[k]["tag"]]
print(new_list)
Try this:
d = {'one': {'tag': 'A'},
'two': {'tag': 'B'},
'three': {'tag': 'C'}}
tag_values = [d[i][j] for i in d for j in d[i]]
>>> print tag_values
['C', 'B', 'A']
You can sort the list afterwards if it matters.
If you have other key/value pairs in the inner dicts, apart from 'tag', you may want to specify the 'tag' keys, like this:
tag_value = [d[i]['tag'] for i in d if 'tag' in d[i]]
for the same result. If 'tag' is definitely always there, remove the if 'tag' in d[i] part.
As a side note, never a good idea to call a list 'list', since it's a reserved word in Python.
You can try this:
[i['tag'] for i in dict.values()]
I would do something like this:
untransformed = {
'one': {'tag': 'A'},
'two': {'tag': 'B'},
'three': {'tag': 'C'},
'four': 'bad'
}
transformed = [value.get('tag') for key,value in untransformed.items() if isinstance(value, dict) and 'tag' in value]
It also sounds like you're trying to get some info out of JSON you might want to look into a tool like https://stedolan.github.io/jq/manual/

Python:How to determine whether the dictionary contains multiple keys?

d={'a':1, 'b':2, ...}
if 'a' in d and 'b' in d and ...:
pass
is there a simple way to determine multiple keys at once?
something like:
if ['a', 'b'] in d:
You can do
if all(key in d for key in ['a', 'b', 'c', ...]):
This may be longer than writing them out separately if you're only testing a couple, but as the list of keys to be tested grows longer, this way will be quicker, since you only have to add the key to the list and not write an additional in d and.
d={'a':1, 'b':2, ...}
required_keys = set(('a', 'b', ...))
missing_keys = required_keys.difference(d.keys())
if missing_keys
print "You are missing some keys: ", missing_keys
else:
print "You have all of the required keys"
len(d.keys()) will let you know how many keys are in your dictionary

Adding Items to Python Dictionary

I may have understood this wrong but looking at the examples found in "Learning Python" by O'Reilly I tried to do the following:
>>> d={}
>>> d['h']='GG'
>>> d['f']='JJ'
>>> d['h']='PP'
>>> print d
{'h': 'PP', 'f': 'JJ'}
Now instead of the 'key' 'h' having two entries 'GG' and 'PP' it only has the last entry, the last one replacing the first one.
I want BOTH in the same key.
>>> d['h']+='RR'
>>> print d
{'h': 'PPRR', 'f': 'JJ'}
Again this doesn't work, what I wanted was not a concatenated string but comma-separated entires.
I am confused why this does not work.
Your use-case is handled nicely by the collections.defaultdict() type instead:
>>> from collections import defaultdict
>>> d = defaultdict(list)
>>> d['h'].append('GG')
>>> d['f'].append('JJ')
>>> d['h'].append('PP')
>>> d
defaultdict(<type 'list'>, {'h': ['GG', 'PP'], 'f': ['JJ']})
A regular dictionary maps one key to one value, if you want that value to be a list, then you should make it a list, and append to the list instead.
You don't have to use a defaultdict() object, you can always make your values explicit lists:
>>> d = {}
>>> d['h'] = ['GG']
>>> d['f'] = ['JJ']
>>> d['h'].append('PP')
>>> print d
{'h': ['GG', 'PP'], 'f': ['JJ']}
but now you need to create the lists explicitly. The latter problem can then be circumvented again by using dict.setdefault():
>>> d = {}
>>> d.setdefault('h', []).append('GG')
>>> d.setdefault('f', []).append('JJ')
>>> d.setdefault('h', []).append('PP')
which is just a more verbose way of using what defaultdict() objects can provide directly.
It sounds like you want your dictionary to have 'h' map to a list of strings, which you can do as follows:
>>> d={}
>>> d['f']='JJ'
>>> d['h']=['PP']
>>> d['h'].append( 'RR' )
>>> d
{'h': ['PP', 'RR'], 'f': 'JJ'}
If you want all the keys of your dictionary to map to a list (instead of just 'h'), you can use collection.defaultdict as demonstrated in #MartijnPieters's answer.

How to split two times?

I have a long string with some data… I need to split it by & and then each of it by = and create from it pair like key: value from the last split… Is it possible to do without big loops? Something like:
video_data = video_data.split('&')
video_data = {key:value for value.split('=') in video_data.iteritems()}
It looks like you're trying to parse a query string. Python already has a method for this, and that will also handle multiple values for keys and automatically create a dict of key->list of values for you:
from urlparse import parse_qs
s = 'a=3&b=5&a=4'
qs = parse_qs(s)
# {'a': ['3', '4'], 'b': ['5']}
As noted by J.F. Sebastian in comments:
Note: it does more than just splits on & and = e.g :
parse_qs("a=%21&b=urlencoded")
# {'a': ['!'], 'b': ['urlencoded']}
Either urlparse.parse_qs() or urlparse.parse_qsl() would do this job for you, better, faster and more robustly:
>>> example = 'foo=bar&ham=eggs&answer=42'
>>> from urlparse import parse_qs, parse_qsl
>>> parse_qs(example)
{'answer': ['42'], 'foo': ['bar'], 'ham': ['eggs']}
>>> parse_qsl(example)
[('foo', 'bar'), ('ham', 'eggs'), ('answer', '42')]
>>> dict(parse_qsl(example))
{'answer': '42', 'foo': 'bar', 'ham': 'eggs'}
Use one or the other depending on how much you need to support keys appearing multiple times in the query string.
But you really wanted to do this yourself with a dict comprehension, you need to nest the .split() call into a tuple:
video_data = {key: value for item in video_data.split('&') for key, value in (item.split('='),)}
but the same parse is just easier without a dict comprehension; using a generator expression instead to produce a sequence of key-value pairs for the dict() factory instead:
video_data = dict(item.split('=') for item in video_data.split('&'))
Demo:
>>> example = 'foo=bar&ham=eggs&answer=42'
>>> {key: value for item in example.split('&') for key, value in (item.split('='),)}
{'answer': '42', 'foo': 'bar', 'ham': 'eggs'}
>>> dict(item.split('=') for item in example.split('&'))
{'answer': '42', 'foo': 'bar', 'ham': 'eggs'}

Categories