I'm working on a small framework and I've found a place where it would be beneficial to save a dictionary key as variable.
The problem I have is that the dictionary may have any number of layers, so it's not just a case of storing the final key. For example in the below I am accessing ['dig']['result'], but that could equally be ['output'] or ['some']['thing']['strange']
if result:
if self.cli_args.json:
pprint(result)
else:
print result['dig']['result']
I could save the key as a string and use eval() in something such as:
key="['test']"
test_dict = { "test" : "This works" }
eval("test_dict" + key)
>>> 'This works'
But eval is really dirty right? :-)
Is there a nice / pythonic way to do this?
To handle an arbitrary depth of key nesting, you can iterate over a sequence (e.g. tuple) of the keys:
>>> d = {'a': {'b': {'c': 'd'}}}
>>> d['a']['b']['c']
'd'
>>> keys = ('a', 'b', 'c') # or just 'abc' for this trivial example
>>> content = d
>>> for k in keys:
content = content[k]
>>> content
'd'
>>> def access(o,path):
... for k in path.split('/'):
... o = o[k]
... return o
...
>>> access({'a': {'b': {'c': 'd'}}},'a/b/c')
'd'
Related
I am trying to create a player_def function that will make creating a dictionary a little easier.
Looking at it now, this is probably kind of dumb because I can just do players["betts"]["avg"]=340, right? Anyway, to understand how Python works I would be grateful if any of you can explain why the following code is returning a key error instead of creating a nested dictionary.
def player_def(x,y,z):
players[x][y]=z
player_def("betts","avg",340)
print(players["betts"])
The easiest solution would be to use a collections.defaultdict:
from collections import defaultdict
players = defaultdict(dict)
def player_def(x,y,z):
players[x][y] = z
player_def("betts","avg",340)
print(players["betts"])
# {'avg': 340}
We define players as a defaultdict of dict. When we do:
players["betts"]["avg"] = 340
if players doesn't yet have a betts key, a new one is created on the fly with an empty dict as value. So, we can add "avg": 340 to this new dict.
Do you mean this? I'm sorry, but my query does not respond to your problem in a comment, so I had to put it as a possible solution / explanation.
>>> d={}
>>> d
{}
>>> d['a'] = {'b' : {'c','d','e'} }
>>> d
{'a': {'b': {'c', 'e', 'd'}}}
>>>
>>> d['a']['b']
{'c', 'e', 'd'}
///EDIT: So when the dictionary already exists, then you can change its contents. However, if you want to add a new pair (to the right side of an existing key), you must add to the existing key, a non-existent, just above syntax. I guess I explain that complicated, sorry.
>>> d['a']['b'] = "4"
>>> d
{'a': {'b': '4'}}
>>> d['a']['b'] = ["4","test","hello"]
>>> d
{'a': {'b': ['4', 'test', 'hello']}}
>>> d['a']['b'] = (1,2,3,4)
>>> d
{'a': {'b': (1, 2, 3, 4)}}
>>>
Another example from Python console:
>>> test = {}
>>> test['betts']['avg'] = 300
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
KeyError: 'betts'
>>> test['betts'] = {}
>>> test['betts']['avg'] = 300
>>> test
{'betts': {'avg': 300}}
>>>
I have a nested dictionary whose structure looks like this
{"a":{},"b":{"c":{}}}
Every key is a string and every value is a dict.
I need to replace every empty dict with "". How would I go about this?
Use recursion:
def foo(the_dict):
if the_dict == {}:
return ""
return {k : foo(v) for k,v in the_dict.items()}
Here you have a live example
Checking values for an empty dict recursively, and replacing with empty string if so.
>>> d = {"a":{},"b":{"c":{}}}
>>> def replace_empty(d):
... for k, v in d.items():
... if not bool(v):
... d[k] = ""
... else:
... d[k] = replace_empty(v)
... return d
>>> replace_empty(d)
{'a': '', 'b': {'c': ''}}
Let the dictionary be cd then:-
>>> def foo(cd):
... for x in cd:
... if cd[x] == {}:
... cd[x] = ""
... else: foo(cd[x])
... return cd
>>> cd = {"a":{},"b":{"c":{}}}
>>> foo(cd)
{'a': '', 'b': {'c': ''}}
The thinking process goes like this:-
Firstly make a processor which will process your dictionary and replace all empty dictionaries with empty strings. Then just give all the dictionaries the opportunity to meet that processor.
I have dictionary with three-level nesting, eg:
d = {
'sp1':{
'a1':{'c1':2,'c2':3},
'a2':{'c3':1,'c4':4}
},
'sp2':{
'a1':{'c1':3,'c2':3},
'a2':{'c3':2,'c4':0}
}
}
All 2nd-level dictionaries contain the same elements, so I want to change it to
d2 = {'a1':{'c1':{'sp1':2,'sp2':3}, 'c2':{'sp1':3,'sp2':3}}}
i.e. essentially switch nesting order. But when I write code like
d2 = {}
d2['a1']['c1']['sp1'] = 2
It just throws KeyError with whatever values happens to be 'a1'. How do I perform such operation?
If you are doing it manually like the snippet you tried, this is how you should be doing it:
>>> d = {
... 'sp1':{
... 'a1':{'c1':2,'c2':3},
... 'a2':{'c3':1,'c4':4}
... },
... 'sp2':{
... 'a1':{'c1':3,'c2':3},
... 'a2':{'c3':2,'c4':0}
... }
... }
>>>
>>> e = {}
>>> e['a1'] = {}
>>> e['a1']['c1'] = {}
>>> e['a1']['c1']['sp1'] = d['sp1']['a1']['c1']
>>> e['a1']['c2'] = {}
>>> e['a1']['c2']['sp1'] = d['sp1']['a1']['c2']
>>> e['a2'] = {}
>>> e['a2']['c1'] = {}
>>> e['a2']['c2'] = {}
>>> e['a1']['c1']['sp2'] = d['sp2']['a1']['c1']
>>> e['a1']['c2']['sp2'] = d['sp2']['a1']['c2']
>>> e
{'a1': {'c2': {'sp1': 3, 'sp2': 3}, 'c1': {'sp1': 2, 'sp2': 3}}}
>>>
But it is unclear as to why you are doing it. As OmnipotentEntity suggested in the comments, may be you need to use a different data structure to store the data.
To do this, you can use defaultdict, which allows you to define a default initialization action on a dict.
In your case, you want a reverse-order recursive defaultdict, with a classmethod
reverse_recursive_make() which unrolls and reverses the key order:
when passed in a key-value pair or None, returns a (toplevel) dict
when passed in a dict, recurses into each of the {k:v} pairs
I'm not going to write the code for that because what you want can be much more easily achieved with SQL, like I commented.
FOOTNOTE: your version with lambdas (comment below) is perfect.
(If you insist on using dicts, and not some other data structure)
something like this should work
d_final = {}
for k in d.keys():
d2 = d[k]
for k2 in d2.keys():
d3 = d2[k2]
for k3 in d3.keys():
d4 = d_final.get(k2,{})
d4[k] = d3[k3]
d_final[k2] = d4
I may have my indexing off a little, but that should be about right.
I am trying to find a way of deleting duplicate shaders in Maya using Python Dictionaries.
Here is what I'm doing:
I want to put all maya shaders into a dictionary as keys and put the corresponding texture file as the value. Then I want the script to run through the dictionary and find any keys that share the same value and stuff them into an array or another dictionary.
This is basically what I have right now:
shaders_dict = {'a': somePath, 'b': somePath,
'c': differentPath, 'd': differentPath}
duplicate_shaders_dict = {}`
how can I now run through that dictionary to compile another dictionary that looks something like this:
duplicate_shaders_dict = {'b':somePath, 'd':differentPath }
And the tricky part being since there are duplicates I want the script to skip the original key so it doesn't also get stuffed in to duplicate shaders dictionary.
I would probably do something like this. First, make the inverse dictionary:
>>> from collections import defaultdict
>>>
>>> shaders_dict = {'a':'somePath', 'b':'somePath', 'c':'differentPath', 'd':'differentPath'}
>>>
>>> inverse_dict = defaultdict(list)
>>> for k,v in shaders_dict.iteritems():
... inverse_dict[v].append(k)
...
>>> inverse_dict
defaultdict(<type 'list'>, {'differentPath': ['c', 'd'], 'somePath': ['a', 'b']})
This basically inverts the dictionary by looping over every key, value pair and appending the key to a list associated with the value.
Then split this:
>>> first_shaders_dict = {}
>>> duplicate_shaders_dict = {}
>>> for v, ks in inverse_dict.iteritems():
... first, rest = ks[0], ks[1:]
... first_shaders_dict[first] = v
... for r in rest:
... duplicate_shaders_dict[r] = v
...
>>> first_shaders_dict
{'a': 'somePath', 'c': 'differentPath'}
>>> duplicate_shaders_dict
{'b': 'somePath', 'd': 'differentPath'}
Hmm. This assumes that the texture files are hashable and so can serve as dictionary keys. If they're not, then I'd have to work around that. Also, since as #freespace notes there's no ordering here, if you wanted a particular order we'd have to iterate over sorted keys or the like.
--
Update: I didn't like the above much. Shorter itertools-based version:
>>> import itertools
>>> shaders_dict = {'a':'somePath', 'b':'somePath', 'c':'differentPath', 'd':'differentPath'}
>>> keys = sorted(sorted(shaders_dict),key=shaders_dict.get)
>>> by_val = [(v, list(ks)) for v, ks in itertools.groupby(keys, shaders_dict.get)]
>>> first_dict = dict((ks[0],v) for v,ks in by_val)
>>> duplicate_dict = dict((k,v) for v,ks in by_val for k in ks[1:])
>>> first_dict
{'a': 'somePath', 'c': 'differentPath'}
>>> duplicate_dict
{'b': 'somePath', 'd': 'differentPath'}
One simple solution is to reverse the dictionary. Given:
>>> d = {'a': 'somePath', 'b': 'somePath',
... 'c': 'differentPath', 'd': 'differentPath'}
You can reverse it like this:
>>> r = dict((v,k) for k,v in d.iteritems())
Which gives you:
>>> r
{'differentPath': 'd', 'somePath': 'b'}
And if you reverse that, you have the original dictionary with duplicates removed:
>>> d = dict((v,k) for k,v in r.iteritems())
>>> d
{'b': 'somePath', 'd': 'differentPath'}
For example I have
x = ['a','b','c']
I need to convert it to:
y['a']['b']['c'] = ''
Is that possible?
For the background, I have a config file which contains dotted notation that points to a place in some json data. I'd like to use the dotted notation string to access that specific data in the json file. For example, in the config:
path_to_data = "user.name.first_name"
I'd like my script to recognize that as:
json_data["user"]["name"]["first_name"]
so I can get the value of the first_name field. I converted the original string into a list, and now I don't know how to convert it to a nested dict.
EDIT: There is an existing data structure that I need to apply the dict with. Let's say:
m = {'a': {'b': {'c': 'lolcat'}}}
so that
m['a']['b']['c']
gives me 'lolcat'. If I get the right dictionary structure (as some of the replies did), I would still need to apply this to the existing dictionary 'm'.
So, again, I get this from a config file:
c = 'a.b.c'
That I converted to a list, thinking this will make things easier:
x = ['a','b','c']
Now I have a json-like data structure:
m = {'a': {'b': {'c': 'lolcat'}}}
So the nested dict generated from 'x' should be able to traverse 'm' so that
m['a']['b']['c']
gets me the cat.
li = ['a','b','c']
d = reduce(lambda x, y: {y:x}, reversed(li+['']))
print(d)
print(d['a']['b']['c'])
I guess you also want to include a value in the end. This works for that too:
def get_value(d, l):
if len(l) > 1:
return get_value(d[l[0]], l[1:])
return d[l[0]]
def add_keys(d, l, c=None):
if len(l) > 1:
d[l[0]] = _d = {}
d[l[0]] = d.get(l[0], {})
add_keys(d[l[0]], l[1:], c)
else:
d[l[0]] = c
def main():
d = {}
l1 = ['a', 'b', 'c', 'd']
c1 = 'letters'
l2 = [42, "42", (42,)]
c2 = 42
add_keys(d, l1, c1)
print d
add_keys(d, l2, c2)
print d
if __name__ == '__main__':
main()
It prints:
{'a': {'b': {'c': {'d': 'letters'}}}}
{'a': {'b': {'c': {'d': 'letters'}}}, 42: {'42': {(42,): 42}}}
letters
42
So it surely works. Recursion for the win.
>>> x = ['a','b','c']
>>> y={}
>>> y[x[-1]]=""
>>> x.pop(-1)
'c'
>>> for i in x[::-1]:
... y={i:y}
...
>>> y
{'a': {'b': {'c': ''}}}
>>> y['a']['b']['c']
''
This will work.
#!/usr/bin/python2
from __future__ import print_function
x = ['a','b','c']
def ltod(l):
rv = d = {}
while l:
i = l.pop(0)
d[i] = {}
d = d[i]
return rv
d = ltod(x)
print(d)
print(d["a"]["b"]["c"])
d["a"]["b"]["c"] = "text"
print(d["a"]["b"]["c"])
Outputs:
{'a': {'b': {'c': {}}}}
{}
text
Find below sample that is not very beautiful but quite simple:
path_to_data = "user.name.first_name"
keys = path_to_data.split('.')
t = []
for key in keys[::-1]: # just to iterate in reversed order
if not t:
t.append({k:{}})
else:
t[-1] = ({k: t[-1]})
#t[0] will contain your dictionary
A general solution would be to use collections.defaultdict to create a nested dictionary. Then override __setitem__ for whatever behavior you'd like. This example will do the string parsing as well.
from collections import defaultdict
class nesteddict(defaultdict):
def __init__(self):
defaultdict.__init__(self, nesteddict)
def __setitem__(self, key, value):
keys = key.split('.')
for key in keys[:-1]:
self = self[key]
defaultdict.__setitem__(self, keys[-1], value)
nd = nesteddict()
nd['a.b.c'] = 'lolcat'
assert nd['a']['b']['c'] == 'lolcat'