Switchable dot access to Python dicts?
dictionary, getattr, python, setattr
Solution
If you set `self.__dict__ = self`, then the dict will automatically become "dottable". You can turn off the "dot-ability" by setting `self.__dict__ = {}`. The key-value pairs will still be accessible through indexing, however. This idea comes mainly from katrielalex's bio page:
class DottableDict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self.__dict__ = self
def allowDotting(self, state=True):
if state:
self.__dict__ = self
else:
self.__dict__ = dict()
d = DottableDict()
d.allowDotting()
d.foo = 'bar'
print(d['foo'])
# bar
print(d.foo)
# bar
d.allowDotting(state=False)
print(d['foo'])
# bar
print(d.foo)
# AttributeError: 'DottableDict' object has no attribute 'foo'
Remember the Zen of Python, however:
There should be one-- and preferably only one --obvious way to do it.
By restricting yourself to the standard syntax for dict access, you improve readability/maintainability for yourself and others.
How it works:
When you type `d.foo`, Python looks for `'foo'` in a number of places, one of which is in `d.__dict__`. (It also looks in `d.__class__.__dict__`, and all the `__dict__`s of all the bases listed in `d.__class__.mro()`... For the full details of attribute lookup, see this excellent article by Shalabh Chaturvedi).
Anyway, the important point for us is that all the key-value pairs in `d.__dict__` can be accessed with dot notation.
That fact means we can get dot-access to the key-value pairs in `d` by setting `d.__dict__` to `d` itself! `d`, after all, is a dict, and `d.__dict__` expects a dict-like object. Notice this is also memory-efficient. We are not copying any key-value pairs, we're simplying directing `d.__dict__` to an already existent dict.
Furthermore, by assigning `d.__dict__` to `dict()`, we effectively turn off dot-access to the key-value pairs in `d`. (This does not completely disable dot-access -- key-value pairs in `d.__class_.__dict__` for instance, can still be accessed through dot notation. Thank goodness that's true or you wouldn't be able to call the `allowDotting` method again!)
Now you might be wondering if this deletes all the key-value pairs in `d` itself. The answer is no.
The key-value pairs are not stored in the `__dict__` attribute. In fact, a normal dict does not have a `__dict__` attribute. So setting `d.__dict__ = {}` simply resets the dict to a neutral condition. We could have used
del self.__dict__
insteaad of
self.__dict__ = dict()
too. However, since the `DottableDict` is given a `__dict__` attribute in `__init__`, it seems cleaner to me to allow instances of `DottableDict` to always have a `__dict__` attribute.
In the comments you note:
Steps: turn off access, set d.foo to 'bar', turn on access, d.foo is gone from everywhere.
To preserve attributes such as `d.foo` which were set while `allowDotting` has been turned off, you'll need to store the alternate dict to which `self.__dict__` has been set.
class DottableDict(dict):
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
self['_attributes'] = dict()
self.allowDotting()
def allowDotting(self, state=True):
if state:
self.update(self['_attributes'])
self.__dict__ = self
else:
self.__dict__ = self['_attributes']
d = DottableDict()
d.allowDotting(state=False)
d.foo = 'bar'
d.allowDotting(state=True)
print(d.foo)
# bar
d.allowDotting(state=False)
print(d.foo)
# bar
d.allowDotting(state=True)
print(d.foo)
# bar
By conventional, attributes that start with a single underscore are understood to be private, implementation details. I'm extending the convention here by introducing a private key, `'_attribute'` into the dict.
Problem
I haven't seen a toggleable version of dictionary dot-access yet. My first-pass attempt here doesn't work: ``` class DottableDict (dict): def allowDotting (self, state=True): if state: self.__setattr__ = dict.__setitem__ self.__getattr__ = dict.__getitem__ else: del self.__setattr__ del self.__getattr__ >>> import dot >>> d = dot.DottableDict() >>> d.allowDotting() >>> d.foo = 'bar' >>> d {} >>> d.foo 'bar' >>> d.__dict__ {'__setattr__': <slot wrapper '__setitem__' of 'dict' objects>, 'foo': 'bar', '__getattr__': <method '__getitem__' of 'dict' objects>} >>> d.allowDotting(False) >>> d.__dict__ {'foo': 'bar'} ``` I think the signatures don't match up between setattr and setitem. My second pass also seems like it should work, but fails in the same ways: ``` class DottableDict (dict): def dotGet (self, attr): return dict.__getitem__(self, attr) def dotSet (self, attr, value): return dict.__setitem__(self, attr, value) def allowDotting (self, state=True): if state: self.__getattr__ = self.dotGet self.__setattr__ = self.dotSet else: del self.__setattr__ del self.__getattr__ ```