Specifically, in regards to the question:
What changes if team is a dictionary instead? e.g. if team is not None and team['captain'] is not None... etc
?
In case that teams
is a dict
object instead, I would suggest using something like dotwiz
, a handy library I've created to enable dot-access for dictionary objects.
Use this alongside set_default_for_missing_keys()
, in case a nested key might not exist:
from dotwiz import DotWiz, set_default_for_missing_keys
# only once
set_default_for_missing_keys(DotWiz())
team = {'captain': {'address': {'zipcode': 'test'}}}
team = DotWiz(team)
if team.captain.address.zipcode:
print('hello')
team = DotWiz({'key': 'value'})
if team.captain.address.zipcode:
print('world!')
Benchmarks
For in-depth benchmarks including comparison against other libraries, check out the Benchmarks section in the docs.
If anyone's curious, I put together a small performance comparison (create and __getitem__
times) with addict
, in the case when a nested path doesn't exist.
For completeness I've also included a comparison with Dot
, as suggested by @AKX in the comments; note that this implementation does not handle nested containers (i.e. within lists
) currently. I've also went ahead and included the "simplest" nested dict approach by raising a KeyError
, as also suggested.
from timeit import timeit
# pip install addict dotwiz
import addict
import dotwiz
# only once
dotwiz.set_default_for_missing_keys(dotwiz.DotWiz())
# implementation for Dot, as suggested by @AKX
class NotDot:
__bool__ = lambda self: False
__getattr__ = lambda self, name: self
NOT_DOT = NotDot()
class Dot:
__slots__ = ('get', )
def __init__(self, target):
self.get = target.get
def __getattr__(self, name, NOT_DOT=NOT_DOT):
val = self.get(name, NOT_DOT)
if type(val) is dict:
return Dot(val)
return val
# nested dict approach, as suggested by @AKX
def nested_dict_value(d):
try:
return d['captain']['address']['zipcode'] is not None
except (TypeError, KeyError): # presumably team/captain/address is not a dict, or doesn't have the key
return False
if __name__ == '__main__':
d1 = {'captain': {'address': {'zipcode': 'test'}}}
d2 = {'key': 'value'}
n = 100_000
print('dict -> d1: ', round(timeit('nested_dict_value(d1)', number=n, globals=globals()), 3))
print('Dot -> d1: ',
round(timeit('team = Dot(d1); team.captain.address.zipcode', number=n, globals=globals()), 3))
print('DotWiz -> d1: ',
round(timeit('team = dotwiz.DotWiz(d1); team.captain.address.zipcode', number=n, globals=globals()), 3))
print('addict -> d1: ',
round(timeit('team = addict.Dict(d1); team.captain.address.zipcode', number=n, globals=globals()), 3))
print()
print('dict -> d2: ', round(timeit('nested_dict_value(d2)', number=n, globals=globals()), 3))
print('Dot -> d2: ',
round(timeit('team = Dot(d2); team.captain.address.zipcode', number=n, globals=globals()), 3))
print('DotWiz -> d2: ',
round(timeit('team = dotwiz.DotWiz(d2); team.captain.address.zipcode', number=n, globals=globals()), 3))
print('addict -> d2: ',
round(timeit('team = addict.Dict(d2); team.captain.address.zipcode', number=n, globals=globals()), 3))
dot = Dot(d1)
dw = dotwiz.DotWiz(d1)
ad = addict.Dict(d1)
print()
print('Dot.get: ', round(timeit('dot.captain.address.zipcode', number=n, globals=globals()), 3))
print('DotWiz.get: ', round(timeit('dw.captain.address.zipcode', number=n, globals=globals()), 3))
print('addict.get: ', round(timeit('ad.captain.address.zipcode', number=n, globals=globals()), 3))
assert 'test' == dotwiz.DotWiz(d1).captain.address.zipcode == addict.Dict(d1).captain.address.zipcode
assert not Dot(d2).captain.address.zipcode
assert not dotwiz.DotWiz(d2).captain.address.zipcode
assert not addict.Dict(d2).captain.address.zipcode
Results on my Mac M1:
dict -> d1: 0.009
Dot -> d1: 0.103
DotWiz -> d1: 0.09
addict -> d1: 0.375
dict -> d2: 0.012
Dot -> d2: 0.067
DotWiz -> d2: 0.087
addict -> d2: 0.381
Dot.get: 0.097
DotWiz.get: 0.007
addict.get: 0.074