Is there a recursive version of the dict.get() built-in?
A very common pattern to do this is to use an empty dict as your default:
d.get('foo', {}).get('bar')
If you have more than a couple of keys, you could use reduce
(note that in Python 3 reduce
must be imported: from functools import reduce
) to apply the operation multiple times
reduce(lambda c, k: c.get(k, {}), ['foo', 'bar'], d)
Of course, you should consider wrapping this into a function (or a method):
def recursive_get(d, *keys):
return reduce(lambda c, k: c.get(k, {}), keys, d)
recursive n-th child dict.get() - efficiency?
One suggestion I have is to give split()
a second argument. You can do something simpler like:
parent, rest = string.split(".", 1)
Other than that, I see no immediate issues with the code.
You can also do this without recursion:
def recursive_dict_get(item, string, default=False):
for s in string.split('.'):
if (isinstance(item, dict) and s in item):
item = item[s]
else:
return default
return item
Recursive DotDict
I don't see where you are copying the values in the constructor. Here DotDict is always empty because of that. When I added the key assignment, it worked:
class DotDict(dict):
"""
a dictionary that supports dot notation
as well as dictionary access notation
usage: d = DotDict() or d = DotDict({'val1':'first'})
set attributes: d.val2 = 'second' or d['val2'] = 'second'
get attributes: d.val2 or d['val2']
"""
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def __init__(self, dct):
for key, value in dct.items():
if hasattr(value, 'keys'):
value = DotDict(value)
self[key] = value
dct = {'scalar_value':1, 'nested_dict':{'value':2, 'nested_nested': {'x': 21}}}
dct = DotDict(dct)
print dct.nested_dict.nested_nested.x
It looks a bit dangerous and error prone, not to mention source of countless surprises to other developers, but seems to be working.
Recursive function to return a dictionary in Python
Your code is almost correct. It has to be adjusted a little, though.
More specifically,
element
is a file or directory name (not path). If it is a subdirectory or file in a subdirectory the value ofif os.path.isfile(element)
andelif os.path.isdir(element)
will be always False. Hence, replace them withif os.path.isfile(os.path.join(root, element))
andelif os.path.isdir(os.path.join(root, element))
respectively.Similarly,
with open(element)
should be replaced bywith open(os.path.join(root,element))
.When reading the file's first line, you have to store the path and that line in a dictionary.
That dictionary has to be updated when calling the recursive function in
elif os.path.isdir(element)
.
See below for the complete snippet:
import os
def search(root):
my_dict = {} # this is the final dictionary to be populated
for element in os.listdir(root):
if os.path.isfile(os.path.join(root, element)):
try:
with open(os.path.join(root, element)) as file:
my_dict[os.path.join(root, element)] = file.readline() # populate the dictionary
except UnicodeDecodeError:
# This exception handling has been put here to ignore decode errors (some files cannot be read)
pass
elif os.path.isdir(os.path.join(root, element)):
my_dict.update(search(os.path.join(root,element))) # update the current dictionary with the one resulting from the recursive call
return my_dict
print(search('.'))
It prints a dictionary like below:
{
"path/file.csv": "name,surname,grade",
"path/to/file1.txt": "this is the first line of file 1",
"path/to/file2.py": "import os"
}
For the sake of readability, os.path.join(root, element)
can be stored in a variable, then:
import os
def search(root):
my_dict = {} # this is the final dictionary to be populated
for element in os.listdir(root):
path = os.path.join(root, element)
if os.path.isfile(path):
with open(path) as file:
my_dict[path] = file.readline()
elif os.path.isdir(path):
my_dict.update(search(path))
return my_dict
print(search('.'))
recursive access to dictionary and modification
You can use the reduce()
function to traverse a series of nested dictionaries:
def get_nested(d, path):
return reduce(dict.__getitem__, path, d)
Demo:
>>> def get_nested(d, path):
... return reduce(dict.__getitem__, path, d)
...
>>> my_dict = {'key1': {'key2': {'foo': 'bar', 'key3': {'key4': {'key5': 'blah'}}}}}
>>> get_nested(my_dict, ('key1', 'key2', 'key3', 'key4', 'key5'))
'blah'
This version throws an exception when a key doesn't exist:
>>> get_nested(my_dict, ('key1', 'nonesuch'))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in get_nested
KeyError: 'nonesuch'
but you could replace dict.__getitem__
with lambda d, k: d.setdefault(k, {})
to have it create empty dictionaries instead:
def get_nested_default(d, path):
return reduce(lambda d, k: d.setdefault(k, {}), path, d)
Demo:
>>> def get_nested_default(d, path):
... return reduce(lambda d, k: d.setdefault(k, {}), path, d)
...
>>> get_nested_default(my_dict, ('key1', 'nonesuch'))
{}
>>> my_dict
{'key1': {'key2': {'key3': {'key4': {'key5': 'blah'}}, 'foo': 'bar'}, 'nonesuch': {}}}
To set a value at a given path, traverse over all keys but the last one, then use the final key in a regular dictionary assignment:
def set_nested(d, path, value):
get_nested_default(d, path[:-1])[path[-1]] = value
This uses the get_nested_default()
function to add empty dictionaries as needed:
>>> def set_nested(d, path, value):
... get_nested_default(d, path[:-1])[path[-1]] = value
...
>>> my_dict = {'key1': {'key2': {'foo': 'bar'}}}
>>> set_nested(my_dict, ('key1', 'key2', 'key3', 'key4', 'key5'), 'blah')
>>> my_dict
{'key1': {'key2': {'key3': {'key4': {'key5': 'blah'}}, 'foo': 'bar'}}}
Python Recursive function to fetch data from dict
The problem is in these lines -
for key in items:
items += get_data(key)
Here you are modifying items as you're iterating over it. So in the last iteration, your items
ends up getting with the same key multiple times; you can add a logging statement to see which key is being used to call get_data
.
You want to obtain all the new items separately, and then update items after the iteration is done -
new_items = []
for key in items:
new_items += get_data(key)
items += new_items
Accessing value inside nested dictionaries
You can use the get() on each dict. Make sure that you have added the None check for each access.
How to return nested dictionary from function
When you give ['c']['d']
, you slice the list ['c']
using the letter d
, which isin't possible. So what you can do is, correct the slicing:
foo('c')['d']
Or you could alter your function to slice it:
def foo(*args):
d = {'a': 1, 'b': 2, 'c': {'d': 3, 'e': 4}, }
d_old = dict(d) # if case you have to store the dict for other operations in the fucntion
for i in args:
d = d[i]
return d
>>> foo('c','d')
3
In Python, why isn't there a built in function to find a key in a nested dictionary?
Suppose, as several commenters pointed out, the key pointing to your "value of interest" appears multiple times in the nested hierarchy:
sample_dict = {'key1': {'key1': 'value_of_interest'}}
val = sample_dict.recursive_get('key1', None)
What is the value of val
? The inner dict? 'value_of_interest'
? What should it be? Which answer violates the principle of least astonishment?
What about a dict like this?
sample_dict = {
'key1': [
{'key1': 'value_of_interest'},
{'key2': 'less_interesting_value'},
],
}
Should the recursion check at every level for a possible list
or array? It's not uncommon to see dicts like this, especially when formed from JSON.
The point is there's a lot of different dicts out there, which can be nested in a lot of different ways. Python is pretty well-equipped to handle them all fairly elegantly. No definition of recursive_get
that I can think of, though, is.
Finding a key recursively in a dictionary
when you recurse, you need to return
the result of _finditem
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
return _finditem(v, key) #added return statement
To fix the actual algorithm, you need to realize that _finditem
returns None
if it didn't find anything, so you need to check that explicitly to prevent an early return:
def _finditem(obj, key):
if key in obj: return obj[key]
for k, v in obj.items():
if isinstance(v,dict):
item = _finditem(v, key)
if item is not None:
return item
Of course, that will fail if you have None
values in any of your dictionaries. In that case, you could set up a sentinel object()
for this function and return that in the case that you don't find anything -- Then you can check against the sentinel
to know if you found something or not.
Related Topics
String Comparison Doesn't Seem to Work for Lines Read from a File
How to Get Stable Results with Tensorflow, Setting Random Seed
Validation of a Password - Python
Removing Horizontal Lines in Image (Opencv, Python, Matplotlib)
Python Generator That Groups Another Iterable into Groups of N
How to Combine Python Asyncio with Threads
How to Set Headers Using Python's Urllib
How to Highlight Specific X-Value Ranges
Re.Sub Erroring with "Expected String or Bytes-Like Object"
How to Find Unused Functions in Python Code
How to Open Multiple Webpages in Separate Tabs Within a Browser Using Selenium-Webdriver and Python
Convert Alphabet Letters to Number in Python
How to Extract Info Within a #Shadow-Root (Open) Using Selenium Python
What Is an 'Endpoint' in Flask