Fair point,
Do you have actual code where this operation is a performance bottleneck?
I don’t have a piece of code that ius slow solely because of this problem but i do have a benchmark for it.
try/except (OK) : 4.6907 sec
try/except (1st) : 8.7040 sec
try/except (last) : 10.8111 sec
chained .get (OK) : 9.5443 sec
chained .get (1st) : 9.2013 sec
chained .get (last) : 9.3769 sec
safeget (OK) : 8.0189 sec
safeget (1st) : 10.4065 sec
safeget (last) : 13.8650 sec
reduce (OK) : 30.9550 sec
reduce (1st) : 31.7279 sec
reduce (last) : 31.6069 sec
recursive (OK) : 37.9125 sec
recursive (1st) : 36.9373 sec
recursive (last) : 38.2307 sec
deepget (OK) : 3.8801 sec
deepget (1st) : 2.9919 sec
deepget (last) : 3.6450 sec
import timeit
from functools import reduce
# Deep nested dictionary
data = {
"a": {
"b": {
"c": {
"d": {
"e": "final_value"
}
}
}
}
}
# Helpers
def try_except_access(d): # Existing
try:
return d["a"]["b"]["c"]["d"]["e"]
except KeyError:
return "default_value"
def try_except_first_missing(d): # "x"
try:
return d["x"]["b"]["c"]["d"]["e"]
except KeyError:
return "default_value"
def try_except_last_missing(d): # "f"
try:
return d["a"]["b"]["c"]["d"]["f"]
except KeyError:
return "default_value"
def chained_get(d): # Existing
return d.get("a", {}).get("b", {}).get("c", {}).get("d", {}).get("e", "default_value")
def chained_get_first_missing(d): # "x"
return d.get("x", {}).get("b", {}).get("c", {}).get("d", {}).get("e", "default_value")
def chained_get_last_missing(d): # "f"
return d.get("a", {}).get("b", {}).get("c", {}).get("d", {}).get("f", "default_value")
def safeget(dct, *keys):
for key in keys:
try:
dct = dct[key]
except KeyError:
return "default_value"
return dct
def deep_get_reduce(dictionary, keys, default="default_value"):
return reduce(lambda d, key: d.get(key, default) if isinstance(d, dict) else default, keys.split("."), dictionary)
def deep_get_recursive(d, keys, default="default_value"):
if not keys or d is None:
return default
if len(keys) == 1:
return d.get(keys[0], default) if isinstance(d, dict) else default
return deep_get_recursive(d.get(keys[0], {}), keys[1:], default)
# Method name -> statement mapping
benchmarks = {
"try/except (OK)": "try_except_access(data)",
"try/except (1st)": "try_except_first_missing(data)",
"try/except (last)": "try_except_last_missing(data)",
"chained .get (OK)": "chained_get(data)",
"chained .get (1st)": "chained_get_first_missing(data)",
"chained .get (last)": "chained_get_last_missing(data)",
"safeget (OK)": "safeget(data, 'a', 'b', 'c', 'd', 'e')",
"safeget (1st)": "safeget(data, 'x', 'b', 'c', 'd', 'e')",
"safeget (last)": "safeget(data, 'a', 'b', 'c', 'd', 'f')",
"reduce (OK)": "deep_get_reduce(data, 'a.b.c.d.e')",
"reduce (1st)": "deep_get_reduce(data, 'x.b.c.d.e')",
"reduce (last)": "deep_get_reduce(data, 'a.b.c.d.f')",
"recursive (OK)": "deep_get_recursive(data, ['a','b','c','d','e'])",
"recursive (1st)": "deep_get_recursive(data, ['x','b','c','d','e'])",
"recursive (last)": "deep_get_recursive(data, ['a','b','c','d','f'])",
"deepget (OK)": "data.deepget(['a','b','c','d','e'], 'default_value')",
"deepget (1st)": "data.deepget(['x','b','c','d','e'], 'default_value')",
"deepget (last)": "data.deepget(['a','b','c','d','f'], 'default_value')",
}
setup_code = """
from __main__ import (
data,
try_except_access, try_except_first_missing, try_except_last_missing,
chained_get, chained_get_first_missing, chained_get_last_missing,
safeget, deep_get_reduce, deep_get_recursive
)
"""
# Run benchmarks
print("Benchmark: 10M iterations each")
for name, stmt in benchmarks.items():
t = timeit.timeit(stmt, setup=setup_code, number=10_000_000)
print(f"{name:23}: {t:.4f} sec")
My point was that i felt like it completed .get since .get can also be implemented as a method but perhaps this problem is not as common as i thought. i am thankful for your feedback anyway.