Extending SimpleNamespace for Nested Dictionaries

taqkarim

Taq Karim

Posted on April 13, 2020

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)

💖 💪 🙅 🚩
taqkarim
Taq Karim

Posted on April 13, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related