Reputation:
I have string values as,
"a"
"a.b"
"b.c.d"
How to convert them into python dictionary variables as,
a
a["b"]
b["c"]["d"]
The first part of the string (before dot) will become the dictionary name and the rest of the substrings will become the dictionary keys
Upvotes: 2
Views: 5898
Reputation: 71
A little late to the party, but a solution to create the actual dict entries from a dotted path can be done with what is essentially fold-right, where the accumulator starts out as the "leaf" value but changes to a dict after first "iteration" of the reversed path
>>> from functools import reduce
>>> path = 'a.b.c'
>>> leaf_val = 'd'
>>> reduce(lambda accum, val: {val: accum}, reversed(path.split(".")), leaf_val)
{'a': {'b': {'c': 'd'}}}
This is not quite what the question asks, but could be worked into the other solution to avoid eval and lengthy recursive function defs
Upvotes: 1
Reputation: 9131
The pyjq library does something quite similar to this, except you have to explicitly provide a dictionary to be the root, and you have to prefix your strings with a .
to refer to whatever dictionary was your root.
python:
import pyjq
d = { 'a' : { 'b' : { 'c' : 'd' } } }
for path in ['.a', '.a.b', '.b.c.d', '.x']:
print(pyjq.first(path, d))
output:
{'b': {'c': 'd'}}
{'c': 'd'}
None
None
Upvotes: 0
Reputation: 650
I ran into this same problem for parsing ini files with dot-delimited keys in different sections. e.g.:
[app]
site1.ftp.host = hostname
site1.ftp.username = username
site1.database.hostname = db_host
; etc..
So I wrote a little function to add "add_branch" to an existing dict tree:
def add_branch(tree, vector, value):
"""
Given a dict, a vector, and a value, insert the value into the dict
at the tree leaf specified by the vector. Recursive!
Params:
data (dict): The data structure to insert the vector into.
vector (list): A list of values representing the path to the leaf node.
value (object): The object to be inserted at the leaf
Example 1:
tree = {'a': 'apple'}
vector = ['b', 'c', 'd']
value = 'dog'
tree = add_branch(tree, vector, value)
Returns:
tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog'}}}
Example 2:
vector2 = ['b', 'c', 'e']
value2 = 'egg'
tree = add_branch(tree, vector2, value2)
Returns:
tree = { 'a': 'apple', 'b': { 'c': {'d': 'dog', 'e': 'egg'}}}
Returns:
dict: The dict with the value placed at the path specified.
Algorithm:
If we're at the leaf, add it as key/value to the tree
Else: If the subtree doesn't exist, create it.
Recurse with the subtree and the left shifted vector.
Return the tree.
"""
key = vector[0]
tree[key] = value \
if len(vector) == 1 \
else add_branch(tree[key] if key in tree else {},
vector[1:],
value)
return tree
Upvotes: 5
Reputation: 54183
eval
is fairly dangerous here, since this is untrusted input. You could use regex to grab the dict name and key names and look them up using vars
and dict.get
.
import re
a = {'b': {'c': True}}
in_ = 'a.b.c'
match = re.match(
r"""(?P<dict> # begin named group 'dict'
[^.]+ # one or more non-period characters
) # end named group 'dict'
\. # a literal dot
(?P<keys> # begin named group 'keys'
.* # the rest of the string!
) # end named group 'keys'""",
in_,
flags=re.X)
d = vars()[match.group('dict')]
for key in match.group('keys'):
d = d.get(key, None)
if d is None:
# handle the case where the dict doesn't have that (sub)key!
print("Uh oh!")
break
result = d
# result == True
Or even more simply: split on dots.
in_ = 'a.b.c'
input_split = in_.split('.')
d_name, keys = input_split[0], input_split[1:]
d = vars()[d_name]
for key in keys:
d = d.get(key, None)
if d is None:
# same as above
result = d
Upvotes: 4
Reputation: 4318
s = "a.b.c"
s = s.replace(".", "][")+"]" # 'a][b][c]'
i = s.find("]") # find the first "]"
s = s[:i]+s[i+1:] # remove it 'a[b][c]'
s = s.replace("]", "\"]").replace("[", "[\"") # add quotations 'a["b"]["c"]'
# you can now execute it:
v = eval(s)
Upvotes: 2