from types import SimpleNamespace
sn = SimpleNamespace(a=1, b=2, c=3)
vars(sn)
# returns {'a': 1, 'b': 2, 'c': 3}
sn.__dict__
# also returns {'a': 1, 'b': 2, 'c': 3}
Answer from Николай Гордеев on Stack Overflowfrom types import SimpleNamespace
sn = SimpleNamespace(a=1, b=2, c=3)
vars(sn)
# returns {'a': 1, 'b': 2, 'c': 3}
sn.__dict__
# also returns {'a': 1, 'b': 2, 'c': 3}
The straightway dict wont work for heterogeneous lists.
import json
sn = SimpleNamespace(hetero_list=['aa', SimpleNamespace(y='ll')] )
json.loads(json.dumps(sn, default=lambda s: vars(s)))
This is the only way to get back the dict.
python - How to initialize a SimpleNamespace from a dict - Stack Overflow
python - How to use a list of attributes of SimpleNamespace instead of list of dict keys? - Stack Overflow
How to convert a nested python dictionary into a simple namespace? - Stack Overflow
Typing rules for SimpleNamespace - Typing - Discussions on Python.org
2022 answer: now there is a tiny, relatively fast library I have published, called dotwiz, which alternatively can be used to provide easy dot access for a python dict object.
It should, coincidentally, be a little faster than the other options -- I've added a quick and dirty benchmark code I put together using the timeit module below, timing against both a attrdict and SimpleNamespace approach -- the latter of which actually performs pretty solid in times.
Note that I had to modify the
parsefunction slightly, so that it handles nesteddicts within alistobject, for example.
from timeit import timeit
from types import SimpleNamespace
from attrdict import AttrDict
from dotwiz import DotWiz
example_input = {'key0a': "test", 'key0b': {'key1a': [{'key2a': 'end', 'key2b': "test"}], 'key1b': "test"},
"something": "else"}
def parse(d):
x = SimpleNamespace()
_ = [setattr(x, k,
parse(v) if isinstance(v, dict)
else [parse(e) for e in v] if isinstance(v, list)
else v) for k, v in d.items()]
return x
print('-- Create')
print('attrdict: ', round(timeit('AttrDict(example_input)', globals=globals()), 2))
print('dotwiz: ', round(timeit('DotWiz(example_input)', globals=globals()), 2))
print('SimpleNamespace: ', round(timeit('parse(example_input)', globals=globals()), 2))
print()
dw = DotWiz(example_input)
ns = parse(example_input)
ad = AttrDict(example_input)
print('-- Get')
print('attrdict: ', round(timeit('ad.key0b.key1a[0].key2a', globals=globals()), 2))
print('dotwiz: ', round(timeit('dw.key0b.key1a[0].key2a', globals=globals()), 2))
print('SimpleNamespace: ', round(timeit('ns.key0b.key1a[0].key2a', globals=globals()), 2))
print()
print(ad)
print(dw)
print(ns)
assert ad.key0b.key1a[0].key2a \
== dw.key0b.key1a[0].key2a \
== ns.key0b.key1a[0].key2a \
== 'end'
Here are the results, on my M1 Mac Pro laptop:
attrdict: 0.69
dotwiz: 1.3
SimpleNamespace: 1.38
-- Get
attrdict: 6.06
dotwiz: 0.06
SimpleNamespace: 0.06
The dotwiz library can be installed with pip:
$ pip install dotwiz
Based on mujjija's solution this is what I came up with. Full code below
from types import SimpleNamespace
def parse(data):
if type(data) is list:
return list(map(parse, data))
elif type(data) is dict:
sns = SimpleNamespace()
for key, value in data.items():
setattr(sns, key, parse(value))
return sns
else:
return data
info = {
'country': 'Australia',
'number': 1,
'slangs': [
'no worries mate',
'winner winner chicken dinner',
{
'no_slangs': [123, {'definately_not': 'hello'}]
}
],
'tradie': {
'name': 'Rizza',
'occupation': 'sparkie'
}
}
d = parse(info)
assert d.country == 'Australia'
assert d.number == 1
assert d.slangs[0] == 'no worries mate'
assert d.slangs[1] == 'winner winner chicken dinner'
assert d.slangs[2].no_slangs[0] == 123
assert d.slangs[2].no_slangs[1].definately_not == 'hello'
assert d.tradie.name == 'Rizza'
assert d.tradie.occupation == 'sparkie'
If I'm not mistaken, Python doesn't support Tail Call Optimization. So please be careful when using deep recursive functions in Python. For small examples, it should be fine.
Update
Another version. object_hook does the magic of nesting. I prefer this version because I can directly feed them to the jinja2 template engine.
import json
class _DotDict(dict):
__getattr__ = dict.__getitem__
__setattr__ = dict.__setitem__
__delattr__ = dict.__delitem__
def dot(data=None):
if data is []:
return []
return json.loads(json.dumps(data), object_hook=_DotDict) if data else _DotDict()
Edit: Solved, thank you BumbleSpork, and everyone else for your help. What I was looking for is types.SimpleNamespace.
Hey!
I would like to create something like a dict, with a bunch of keys, but access them as Foo.bar rather than foo["bar"]
I know I can use a class with attributes for this:
class Foo:
bar = "this works"
print(Foo.bar) # prints "this works"Is this the best way? Are there classes for this specific use case in the standard library? Thanks!
SimpleNamespace is basically just a nice facade on top of a dictionary. It allows you to use properties instead of index keys. This is nice as it is super flexible and easy to manipulate.
The downside of that flexibility is that it doesn't provide any structure. There is nothing to stop someone from calling SimpleNamespace(x=ax, y=ay) (and del a.z at some point later). If this instance gets passed to your function, the exception occurs when you try to access the field.
In contrast, namedtuple lets you create a structured type. The type will have a name and it will know what fields it is supposed to have. You won't be able to make an instance without each of those field and they can't be removed later. Additionally, the instance is immutable, so you will know that the value in a.x will always be the same.
It's up to you to decide if you need the flexibility that SimpleNamespace gives you, or if you prefer to have the structure and guarantees provided by namedtuple.
I really like the answer about structured versus not, so I'm just providing a concrete example below.
SimpleNamespace will accept keys that begin with _. If you're looking for a quick and easy way to turn, say, JSON you don't control into objects with field names, this is very handy:
d = {"_id": 2342122, "text": "hi there!"} # Elasticsearch gives this id!
e = SimpleNamespace(**d) # works
Name = namedtuple("Name", sorted(d)) # ValueError so we can't do Name(**d)
Note above that you can see that namedtuple gives us a whole extra object that SimpleNamespace never will. Each SimpleNamespace is really a "unique snowflake", whereas namedtuple exists without ever being instantiated with any concrete values. Wherever you have need of abstractions that generalize on concrete values, you probably should prefer it.