Skip to main content

Very simple deep_set and deep_get functions to access nested dicts (or any object) using 'dotted strings' as key.

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dict-deep-4.1.2.tar.gz (5.8 kB view details)

Uploaded Source

Built Distribution

dict_deep-4.1.2-py3-none-any.whl (5.3 kB view details)

Uploaded Python 3

File details

Details for the file dict-deep-4.1.2.tar.gz.

File metadata

  • Download URL: dict-deep-4.1.2.tar.gz
  • Upload date:
  • Size: 5.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.5 CPython/3.8.2 Linux/5.4.0-24-generic

File hashes

Hashes for dict-deep-4.1.2.tar.gz
Algorithm Hash digest
SHA256 73c3bef1fc45d271f5e508e957d383fd6438510f50132559374834430bca5401
MD5 4fc37f859712b414091b8aac30cd3be6
BLAKE2b-256 af4bd14f317d1851869b1b4ac2060f80805a929b34ad9ecd69b3162ca961ad4b

See more details on using hashes here.

File details

Details for the file dict_deep-4.1.2-py3-none-any.whl.

File metadata

  • Download URL: dict_deep-4.1.2-py3-none-any.whl
  • Upload date:
  • Size: 5.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/1.0.5 CPython/3.8.2 Linux/5.4.0-24-generic

File hashes

Hashes for dict_deep-4.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 186aeada606e251771b9e4530c1e89aed855b2ab40e7e8cca16c78bce895e2f5
MD5 6bcb4d8de6539778c0663326afffa450
BLAKE2b-256 436e42316ce0d72ccb58f19213e8ec3d6693454e5cf71b5ae711343f85073a9c

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page