Skip to content

Instantly share code, notes, and snippets.

@cadu-leite
Last active October 31, 2022 17:40
Show Gist options
  • Save cadu-leite/db4e082b9ac0ea290f57956bb0518d8f to your computer and use it in GitHub Desktop.
Save cadu-leite/db4e082b9ac0ea290f57956bb0518d8f to your computer and use it in GitHub Desktop.
Nested Dictionary values through dot path
'''
get key values based on dot path
based on dict or json
d = {
'd1': 1,
'd2': {
'a': 2, 'b': 3, },
'd3': {
'a': 4, 'b': {
'a': 5, 'b': 6, }
}
}
+----------+-----------------+
| dot Path | Expected Output |
|==========+=================|
| "d1" | 1 |
|----------+-----------------|
| "d2.b" | 3 |
|----------+-----------------|
| "d3.b.b" | 6 |
|----------+-----------------|
| "d5" | None |
|----------+-----------------|
| "d1.b.b" | None |
+----------+-----------------+
$> python3 get_key_dot_path.py
Using reduce
=1
=3
=6
=None
=None
in 6.604194641113281e-05 seconds
Using for loop
=1
=3
=6
=None
=None
in 4.6253204345703125e-05 seconds
Using recursion
=1
=3
=6
=None
=None
in 5.7220458984375e-05 seconds
CProfile
========
Using reduce
------------
15001 function calls in 0.010 seconds
Random listing order was used
ncalls tottime percall cumtime percall filename:lineno(function)
5000 0.002 0.000 0.002 0.000 {method 'split' of 'str' objects}
5000 0.003 0.000 0.003 0.000 {built-in method _functools.reduce}
5000 0.005 0.000 0.010 0.000 get_key_dot_path.py:137(find_dot_path_reduce)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Using for loop
--------------
23001 function calls in 0.011 seconds
Random listing order was used
ncalls tottime percall cumtime percall filename:lineno(function)
8000 0.002 0.000 0.002 0.000 {method 'get' of 'dict' objects}
5000 0.002 0.000 0.002 0.000 {method 'split' of 'str' objects}
5000 0.001 0.000 0.001 0.000 {built-in method builtins.hasattr}
5000 0.006 0.000 0.011 0.000 get_key_dot_path.py:149(find_dot_path_for)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
Using recursion
----------------
61001 function calls (57001 primitive calls) in 0.023 seconds
Random listing order was used
ncalls tottime percall cumtime percall filename:lineno(function)
9000 0.002 0.000 0.002 0.000 {method 'pop' of 'list' objects}
5000 0.001 0.000 0.001 0.000 {method 'reverse' of 'list' objects}
12000 0.002 0.000 0.002 0.000 {method 'get' of 'dict' objects}
5000 0.002 0.000 0.002 0.000 {method 'split' of 'str' objects}
9000 0.001 0.000 0.001 0.000 {built-in method builtins.hasattr}
7000 0.001 0.000 0.001 0.000 {built-in method builtins.len}
9000/5000 0.009 0.000 0.015 0.000 get_key_dot_path.py:121(get_key)
5000 0.006 0.000 0.023 0.000 get_key_dot_path.py:117(find_dot_path)
1 0.000 0.000 0.000 0.000 {method 'disable' of '_lsprof.Profiler' objects}
'''
import functools
import operator
import time
# using recursion
def find_dot_path(dct, s):
keys_list = s.split('.')
keys_list.reverse()
def get_key(dct, keys):
key = keys.pop()
if not hasattr(dct, 'get'):
return None
value = dct.get(key, None)
if value is None or len(keys_list) == 0:
return value
else:
return get_key(dct.get(key), keys_list)
return get_key(dct, keys_list)
# using reduce
def find_dot_path_reduce(dct, path):
try:
return functools.reduce(operator.getitem, path.split('.'), dct)
except TypeError:
# case value <> subscriptable type
return None
except KeyError:
# case Key not Exists
return None
# using a loop
def find_dot_path_for(dct, path):
if hasattr(dct, '__getitem__'):
keys = path.split('.')
for key in keys:
try:
dct = dct.get(key, None)
except AttributeError:
return None
return dct
import cProfile
import pstats
if __name__ == '__main__':
d = {
'd1': 1,
'd2': {
'a': 2, 'b': 3, },
'd3': {
'a': 4, 'b': {
'a': 5, 'b': 6, }
}
}
print(f'\nUsing reduce')
start_time = time.time()
print(f'={find_dot_path_reduce(d, "d1")}')
print(f'={find_dot_path_reduce(d, "d2.b")}')
print(f'={find_dot_path_reduce(d, "d3.b.b")}')
print(f'={find_dot_path_reduce(d, "d5")}')
print(f'={find_dot_path_reduce(d, "d1.b.b")}')
print(f'in {time.time() - start_time} seconds')
print(f'\nUsing for loop')
start_time = time.time()
print(f'={find_dot_path_for(d, "d1")}')
print(f'={find_dot_path_for(d, "d2.b")}')
print(f'={find_dot_path_for(d, "d3.b.b")}')
print(f'={find_dot_path_for(d, "d5")}')
print(f'={find_dot_path_for(d, "d1.b.b")}')
print(f'in {time.time() - start_time} seconds')
print(f'\nUsing recursion')
start_time = time.time()
print(f'={find_dot_path(d, "d1")}')
print(f'={find_dot_path(d, "d2.b")}')
print(f'={find_dot_path(d, "d3.b.b")}')
print(f'={find_dot_path(d, "d5")}')
print(f'={find_dot_path(d, "d1.b.b")}')
print(f'in {time.time() - start_time} seconds')
print(f'\nCProfile\n========')
print(f'\nUsing reduce\n-------------')
pr = cProfile.Profile()
pr.enable()
for k in range(1000):
find_dot_path_reduce(d, "d1")
find_dot_path_reduce(d, "d2.b")
find_dot_path_reduce(d, "d3.b.b")
find_dot_path_reduce(d, "d5")
find_dot_path_reduce(d, "d1.b.b")
pr.disable()
ps = pstats.Stats(pr).print_stats()
print(f'\nUsing for loop\n---------------')
pr = cProfile.Profile()
pr.enable()
for k in range(1000):
find_dot_path_for(d, "d1")
find_dot_path_for(d, "d2.b")
find_dot_path_for(d, "d3.b.b")
find_dot_path_for(d, "d5")
find_dot_path_for(d, "d1.b.b")
pr.disable()
ps = pstats.Stats(pr).print_stats()
print(f'\nUsing recursion\n----------------')
pr = cProfile.Profile()
pr.enable()
for k in range(1000):
find_dot_path(d, "d1")
find_dot_path(d, "d2.b")
find_dot_path(d, "d3.b.b")
find_dot_path(d, "d5")
find_dot_path(d, "d1.b.b")
pr.disable()
ps = pstats.Stats(pr).print_stats()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment