lukecampbell lukecampbell - 16 days ago 6
Python Question

Python dict using dot notation and chaining

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.

Answer

You may need to overwrite getattribute for those edge cases and then use the

object.__getattribute__

Have a look at the module dis. But what you wrote is nicer than disassembling.

>>> import dis
>>> def g():
    a.b.c = 4


>>> dis.dis(g)
  2           0 LOAD_CONST               1 (4)
              3 LOAD_GLOBAL              0 (a)
              6 LOAD_ATTR                1 (b)
              9 STORE_ATTR               2 (c)
             12 LOAD_CONST               0 (None)
             15 RETURN_VALUE        
Comments