DEV Community

Taq Karim
Taq Karim

Posted on

Extending SimpleNamespace for Nested Dictionaries

I'm a huge fan of Python3's types.SimpleNamespace. Basically, it allows us to take a dict, like so:

my_dict = {
  "a": 1,
  "b": 2,
  "c": 3,
}

my_dict["a"] # 1
my_dict["b"] # 2, etc

and manage it like this:

from types import SimpleNamespace
my_namespace = SimpleNamespace(a=1, b=2, c=3)

my_namespace.a # 1
my_namespace.b # 2
my_namespace.c # 3

Alternatively, we could also do something like:

from types import SimpleNamespace

my_dict = {
  "a": 1,
  "b": 2,
  "c": 3,
}

my_namespace = SimpleNamespace(**my_dict)
my_namespace.a # 1
my_namespace.b # 2
my_namespace.c # 3

But - what happens if our my_dict is nested? Like so:

my_dict = {
  "a": {
    "d": 4,
  },
  "b": 2,
  "c": 3,
  "e": [5,6,7,{
    "f": 8,
  }]
}

my_namespace = SimpleNamespace(**my_dict)
my_namespace.a # {"d": 4} /womp womp 😭
my_namespace.a.d # raises Exception! 😭😭

In short, SimpleNamespace simply does not support this use case. But that's ok! We can extend SimpleNamespace and build this functionality for ourselves.

Defining RecursiveNamespace


from types import SimpleNamespace

# this is how SimpleNamespace looks when output
SimpleNamespace(**my_dict)
# namespace(a={'d': 4}, b=2, c=3, e=[5, 6, 7, {'f': 8}])

class RecursiveNamespace(SimpleNamespace):

  @staticmethod
  def map_entry(entry):
    if isinstance(entry, dict):
      return RecursiveNamespace(**entry)

    return entry

  def __init__(self, **kwargs):
    super().__init__(**kwargs)
    for key, val in kwargs.items():
      if type(val) == dict:
        setattr(self, key, RecursiveNamespace(**val))
      elif type(val) == list:
        setattr(self, key, list(map(self.map_entry, val)))

# this is how RecursiveNamespace looks when output
RecursiveNamespace(**my_dict)
# RecursiveNamespace(
#    a=RecursiveNamespace(d=4), 
#    b=2, 
#    c=3, 
#    e=[5, 6, 7, RecursiveNamespace(f=8)])

So, what's happening here? We establish a new class, RecursiveNamespace that extends SimpleNamespace. In the __init__ constructor method, we call SimpleNamespace's constructor. Then, we just walk through our dictionary and for value that is also a dictionary or list, we instantiate that with RecursiveNamespace. Ta da.

P.S.

Technically, we don't even really need types.SimpleNamespace here - we can implement this class without by just adding two lines of code:

class RecursiveNamespace2: # without extending SimpleNamespace!

  @staticmethod
  def map_entry(entry):
    if isinstance(entry, dict):
      return RecursiveNamespace(**entry)

    return entry

  def __init__(self, **kwargs):
    for key, val in kwargs.items():
      if type(val) == dict:
        setattr(self, key, RecursiveNamespace(**val))
      elif type(val) == list:
        setattr(self, key, list(map(self.map_entry, val)))
      else: # this is the only addition
        setattr(self, key, val)

Top comments (1)

Collapse
 
tebanep profile image
Esteban Echeverry

Thank you for your post!

If your dictionary is serializable, you might as well use the json module to accomplish the same 'nested' effect:

import json
from types import SimpleNamespace

my_dict = {
  "a": {
    "d": 4,
  },
  "b": 2,
  "c": 3,
  "e": [5,6,7,{
    "f": 8,
  }]
}

my_namespace = json.loads(json.dumps(my_dict), object_hook=lambda item: SimpleNamespace(**item))

assert my_namespace.a.d == 4  # ;D
Enter fullscreen mode Exit fullscreen mode