Skip to main content

Description

Simple 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:

  1. The key is broken down into a list of keys: "customer.address.city" -> ['customer', 'address', 'city']

  2. 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 o

You 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 1

You 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)'

Functions

deep_get accepts:

  • o: required. Any object, usually a dictionary
  • k: required. The key or keys, must be a string or anything accepted by the list() constructor
  • accessor: 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 value
  • getter_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 accept
  • empty_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 instead

Returns o[k]. If o[k] does not exist, should return None (but depends on the callables used).

deep_set accepts:

  • o: see 'deep_get'
  • k: see 'deep_get'
  • v: required, the value that will be set
  • accessor: 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 = value
  • sep: optional, string: see 'deep_get'

No return value

deep_del accepts:

  • 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 / Usage

from 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)))

Release files for dict-deep 4.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dict-deep 4.1.2
File Size Uploaded
dict-deep-4.1.2.tar.gz 5.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dict-deep 4.1.2
File Interpreter ABI Platform
dict_deep-4.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 11.0 kB

Release files / dict-deep-4.1.2.tar.gz

Download URL dict-deep-4.1.2.tar.gz
Size 5.8 kB
Tags Source
SHA-256 checksum
How to use checksums
73c3bef1fc45d271f5e508e957d383fd6438510f50132559374834430bca5401
BLAKE2b-256 checksum
How to use checksums
af4bd14f317d1851869b1b4ac2060f80805a929b34ad9ecd69b3162ca961ad4b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.0.5 CPython/3.8.2 Linux/5.4.0-24-generic

Release files / dict_deep-4.1.2-py3-none-any.whl

Download URL dict_deep-4.1.2-py3-none-any.whl
Size 5.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
186aeada606e251771b9e4530c1e89aed855b2ab40e7e8cca16c78bce895e2f5
BLAKE2b-256 checksum
How to use checksums
436e42316ce0d72ccb58f19213e8ec3d6693454e5cf71b5ae711343f85073a9c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via poetry/1.0.5 CPython/3.8.2 Linux/5.4.0-24-generic

Release history Release notifications | RSS feed

This release

4.1.2 This release

2 release files

4.1.1

2 release files

4.1.0

2 release files

4.0.3

2 release files

4.0.2

2 release files

4.0.1

2 release files

3.1.1

2 release files

3.1.0

2 release files

3.0.1

2 release files

2.0.2

2 release files

2.0.1

2 release files

1.2.2

2 release files

1.2.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page