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__
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.