Package: dict-deep
DescriptionSimple functions to set or get values from a nested dict structure or in fact a deep structure of any object, because
since version 2 we no longer assume we are dealing with dicts.You may use a custom accessor or pass your own getter, setter, deleter callables so that you can traverse a nested
structure of any kind of object.This module DOES NOT implement dotted notation as an alternative access method for dicts.
I generally do not like changing python dicts to enable dot notation, hence no available
package fitted my needs for a simple deep accessor.NEW IN VERSION 4:
Since version 3 we make no assumption that we are dealing with dicts, so you can have your nested
structure of any type. However, in version 4 we reintroduce better defaults so that for those that
are indeed working with nested dicts the default values shall be enough without having to define an
accessor or a getter.Notes:
With deep_get, you could use 'lambda o, k: o[k]' or 'lambda o, k: o.get(k)' as either the getter or the accessor.
The only 'special' thing about the 'getter' function is that when it is invoked with 'o' being a list, it will instead
iterate over the list and call the accessor for each item in the list.In a simplified way, this is how deep_get works:The key is broken down into a list of keys: "customer.address.city" -> ['customer', 'address', 'city']The list of keys is iterated over, calling the getter for each key and the last value retrieved is returned.for k in keys[:-1]:
    if o is None:
        return o
    o = getter(o, k)

o = getter_last_step(o, keys[-1])

return oYou see that getter could be as simple as 'lambda o, k: o.get(k)'. However, by default the code uses a smarter getter as
defined below, which tries to deal properly with nested lists.def __default_getter(o, k):
    if isinstance(o, list):
        return [accessor(i, k) for i in o]
    else:
        return accessor(o, k)If you do not want this checks for nested lists, just pass your own getter which could just as well
be 'lambda o, k: o.get(k)'.The default setter also knows how to deal with nested lists:def __default_setter(o, k, v):
    n_set = 0
    if isinstance(o, list):
        for i in o:
            i[k] = v
            n_set += 1
        return n_set
    else:
        o[k] = v
        return 1You could just as well replace if with your own 'setter=lambda o, k, v: o[k]=v' if you know that
you have no nested lists in your structures and want to avoid the overhead, but in that case you should
also change the getter 'getter=lambda o, k: o.get(k)'.However, if you like the list handling skills of the code but just needs to change the way the value is retrieved,
in this case you pass an accessor only to deep_get or deep_set which could be, say, 'lambda o, k: o.getValueById(k)'Functionsdeep_getaccepts:o: required. Any object, usually a dictionaryk: required. The key or keys, must be a string or anything accepted by the list() constructoraccessor: optional, callable: Takes o, k (object and key) and returns the value. Default accessor is
'lambda o, k: o.get(k) if hasattr(o, "get") else o[k]'getter: optional, callable. If getter is set, accessor is ignored. Takes an object and a key as arguments and returns
a valuegetter_last_step: optional, callable. The getter to be used on the last step (with the last key). By default,
if the last key is a list of keys, it returns a dict {k[0]: o[k[0]], k[1]: o[k[1]]}. If the last object is a list, it returns a list
of dicts [{k[0]: o[0][k[0]]], k[1]: o[0][k[1]]}, {k[0]: o[1][k[0]]], k[1]: o[1][k[1]]}, ...]sep: optional, string: by default it is a dot '.', you can use anything the string function split will acceptempty_list_as_none: bool = False. If true and the return value would be an empty list, returns None instead.list_of_len_one_as_value: bool = False. If true and the return value would be a list with a single item, returns the item insteadReturns o[k]. If o[k] does not exist, should return None (but depends on the callables used).deep_setaccepts:o: see 'deep_get'k: see 'deep_get'v: required, the value that will be setaccessor: optional, callable: see 'deep_get'. For the deep_set function, the default accessor is:
'lambda o, k: o.setdefault(k, dict()) if hasattr(o, "setdefault") else o[k]'getter: optional, callable: see 'deep_get'setter: optional, callable. A callable that takes 3 parameters: o, k, v - where o = any object, k = key, v = valuesep: optional, string: see 'deep_get'No return valuedeep_delaccepts:o: required: see 'deep_get'k: required: see 'deep_get'accessor: optional, callable: see 'deep_get'getter: optional, callable: see 'deep_get'deleter: optional, callable: Takes 2 parameters: o, k (object and key).sep: optional, string: see 'deep_get'Returns an integer with the number of entries that were deleted.Example / Usagefrom dict_deep import deep_get, deep_set, deep_del


i = 0

# 1
i += 1
o = {'a': {'b': {}}}
deep_set(o, "a.b.c", "Hello World")
print("{}: {}".format(i, deep_get(o, "a.b.c")))

# 2
i += 1
o = {}
deep_set(o, ['a', 'b', 'c'], "Hello World")
print("{}: {}".format(i, deep_get(o, "a.b.c")))

# 3
i += 1
o = {}
deep_set(o, "a->b->c", "Hello World", sep="->")
print("{}: {}".format(i, deep_get(o, "a->b->c", sep="->")))

# 4
i += 1
o = {}
deep_set(o, "a->b->c", "Hello World", getter=lambda o, k: o.setdefault(k, dict()), sep="->")
print("{}: {}".format(i, deep_get(o, "a->b->c", sep="->")))

# 5
i += 1
o = {}
keys = 'a.b.c'
keys = keys.split('.')
_ = deep_get(o=o, k=keys[0:-1], accessor=lambda o, k: o.setdefault(k, dict()), sep=".")
_[keys[-1]] = "Hello World"
print("{}: {}".format(i, deep_get(o, keys)))

# 6
i += 1
o = {}
deep_set(o, "1.1.1", 'a', accessor=lambda o, k: o.setdefault(k, dict()))
deep_set(o, "1.1.2", 'Hello World')
deep_set(o, "1.1.3", 'c')
deep_del(o, "1.1.2")
print("{}: {}".format(i, o))

# 7
i += 1
o = {'students': [{'name': 'Joe', 'age': 10, 'gender': 'male'}, {'name': 'Maria', 'age': 12, 'gender': 'female'}]}
keys = ['students', 'name']
print("{}: {}".format(i, deep_get(o, keys)))

# 8
i += 1
keys = ['students', ['name', 'age']]
print("{}: {}".format(i, deep_get(o, keys)))

# 9
i += 1
keys = ['students', 'gender']
deep_set(o, keys, 'Nowadays better not ask')
print("{}: {}".format(i, o))

# 10
i += 1
keys = ['students', 'gender']
deep_del(o, keys)
print("{}: {}".format(i, o))

# 11
i += 1
keys = ['director', 'name']
print("{}: {}".format(i, deep_get(o, keys)))