Ideally what I am aiming to accomplish is a class that extends (or is very similar to) a dict
in Python with the additional capabilities:
- Dot-Notation capable for setting and getting values
- Key-Value capabilities like
dict
(i.e. setitem,getitem) - Can chain dot-notated operations
The goal is if I have something like example = DotDict()
I could do the following against it example.configuration.first= 'first'
and it would instantiate the appropriate DotDict instances under example
with the really painful caveat being that if the operation is not assignment it should simply raise a KeyError
like a dict
would do
Here is what I have naively assembled
class DotDict(dict):
def __getattr__(self, key):
""" Make attempts to lookup by nonexistent attributes also attempt key lookups. """
import traceback
import re
s= ''.join(traceback.format_stack(sys._getframe(1),1))
if re.match(r' File.*\n.*[a-zA-Z]+\w*\.[a-zA-Z]+[a-zA-Z0-9_. ]*\s*=\s*[a-zA-Z0-9_.\'"]+',s):
self[key] = DotDict()
return self[key]
return self[key]
def __setattr__(self, key, value):
if isinstance(value,dict):
self[key] = DotDict(value)
self[key] = value
It works except for some common edge cases, I must say that I absolutely hate this method and there must be a better way. Looking at the stack and running a regular expression on the last line is not a good way to accomplish this.
The heart of the matter is that Python interprets lines of code left to right so when it arrives at a statement like a.b.c = 3
it's first operation is a getattr(a,b)
and not a setattr
so I can't determine easily if the last operation in the stack of operations is an assignment.
What I would like to know is if there is a good way to determine the last operation in the stack of operations or at least if it's a setattr
.
Edit:
This is the solution that I came up with thanks to user1320237's recommendation.
class DotDict(dict):
def __getattr__(self, key):
""" Make attempts to lookup by nonexistent attributes also attempt key lookups. """
if self.has_key(key):
return self[key]
import sys
import dis
frame = sys._getframe(1)
if '\x00%c' % dis.opmap['STORE_ATTR'] in frame.f_code.co_code:
self[key] = DotDict()
return self[key]
raise AttributeError('Problem here')
def __setattr__(self, key, value):
if isinstance(value,dict):
self[key] = DotDict(value)
self[key] = value
There's a little bit more in the actual implementation but it does an awesome job. The way it works is that it inspects the last frame in the stack and checks the byte code for a STORE_ATTR operation which means that the operation being performed is of the a.b.this.doesnt.exist.yet = 'something'
persuasion. I would be curious if this could be done on other interpreters outside of CPython.