Python: load variables in a dict into namespace
Solution 1:
Consider the Bunch
alternative:
class Bunch(object):
def __init__(self, adict):
self.__dict__.update(adict)
so if you have a dictionary d
and want to access (read) its values with the syntax x.foo
instead of the clumsier d['foo']
, just do
x = Bunch(d)
this works both inside and outside functions -- and it's enormously cleaner and safer than injecting d
into globals()
! Remember the last line from the Zen of Python...:
>>> import this
The Zen of Python, by Tim Peters
...
Namespaces are one honking great idea -- let's do more of those!
Solution 2:
Rather than create your own object, you can use argparse.Namespace
:
from argparse import Namespace
ns = Namespace(**mydict)
To do the inverse:
mydict = vars(ns)
Solution 3:
This is perfectly valid case to import variables in one local space into another local space as long as one is aware of what he/she is doing. I have seen such code many times being used in useful ways. Just need to be careful not to pollute common global space.
You can do the following:
adict = { 'x' : 'I am x', 'y' : ' I am y' }
locals().update(adict)
blah(x)
blah(y)
Solution 4:
Importing variables into a local namespace is a valid problem and often utilized in templating frameworks.
Return all local variables from a function:
return locals()
Then import as follows:
r = fce()
for key in r.keys():
exec(key + " = r['" + key + "']")
Solution 5:
The Bunch answer is ok but lacks recursion and proper __repr__
and __eq__
builtins to simulate what you can already do with a dict. Also the key to recursion is not only to recurse on dicts but also on lists, so that dicts inside lists are also converted.
These two options I hope will cover your needs (you might have to adjust the type checks in __elt()
for more complex objects; these were tested mainly on json imports so very simple core types).
- The Bunch approach (as per previous answer) - object takes a dict and converts it recursively.
repr(obj)
will returnBunch({...})
that can be re-interpreted into an equivalent object.
class Bunch(object):
def __init__(self, adict):
"""Create a namespace object from a dict, recursively"""
self.__dict__.update({k: self.__elt(v) for k, v in adict.items()})
def __elt(self, elt):
"""Recurse into elt to create leaf namespace objects"""
if type(elt) is dict:
return type(self)(elt)
if type(elt) in (list, tuple):
return [self.__elt(i) for i in elt]
return elt
def __repr__(self):
"""Return repr(self)."""
return "%s(%s)" % (type(self).__name__, repr(self.__dict__))
def __eq__(self, other):
if hasattr(other, '__dict__'):
return self.__dict__ == other.__dict__
return NotImplemented
# Use this to allow comparing with dicts:
#return self.__dict__ == (other.__dict__ if hasattr(other, '__dict__') else other)
- The SimpleNamespace approach - since
types.SimpleNamespace
already implements__repr__
and__eq__
, all you need is to implement a recursive__init__
method:
import types
class RecursiveNamespace(types.SimpleNamespace):
# def __init__(self, /, **kwargs): # better, but Python 3.8+
def __init__(self, **kwargs):
"""Create a SimpleNamespace recursively"""
self.__dict__.update({k: self.__elt(v) for k, v in kwargs.items()})
def __elt(self, elt):
"""Recurse into elt to create leaf namespace objects"""
if type(elt) is dict:
return type(self)(**elt)
if type(elt) in (list, tuple):
return [self.__elt(i) for i in elt]
return elt
# Optional, allow comparison with dicts:
#def __eq__(self, other):
# return self.__dict__ == (other.__dict__ if hasattr(other, '__dict__') else other)
The RecursiveNamespace class takes keyword arguments, which can of course come from a de-referenced dict (ex **mydict
)
Now let's put them to the test (argparse.Namespace
added for comparison, although it's nested dict is manually converted):
from argparse import Namespace
from itertools import combinations
adict = {'foo': 'bar', 'baz': [{'aaa': 'bbb', 'ccc': 'ddd'}]}
a = Bunch(adict)
b = RecursiveNamespace(**adict)
c = Namespace(**adict)
c.baz[0] = Namespace(**c.baz[0])
for n in ['a', 'b', 'c']:
print(f'{n}:', str(globals()[n]))
for na, nb in combinations(['a', 'b', 'c'], 2):
print(f'{na} == {nb}:', str(globals()[na] == globals()[nb]))
The result is:
a: Bunch({'foo': 'bar', 'baz': [Bunch({'aaa': 'bbb', 'ccc': 'ddd'})]})
b: RecursiveNamespace(foo='bar', baz=[RecursiveNamespace(aaa='bbb', ccc='ddd')])
c: Namespace(foo='bar', baz=[Namespace(aaa='bbb', ccc='ddd')])
a == b: True
a == c: True
b == c: False
Although those are different classes, because they both (a
and b
) have been initialized to equivalent namespaces and their __eq__
method compares the namespace only (self.__dict__
), comparing two namespace objects returns True
. For the case of comparing with argparse.Namespace
, for some reason only Bunch
works and I'm unsure why (please comment if you know, I haven't looked much further as types.SimpleNameSpace
is a built-in implementation).
You might also notice that I recurse using type(self)(...)
rather than using the class name - this has two advantages: first the class can be renamed without having to update recursive calls, and second if the class is subclassed we'll be recursing using the subclass name. It's also the name used in __repr__
(type(self).__name__
).
EDIT 2021-11-27:
-
Modified the
Bunch.__eq__
method to make it safe against type mismatch. -
Added/modified optional
__eq__
methods (commented out) to allow comparing with the originaldict
andargparse.Namespace(**dict)
(note that the later is not recursive but would still be comparable with other classes as the sublevel structs would compare fine anyway).