Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<h1>Attribute Tree</h1> <p>The problem with your first specification is that Python can't tell in <code>__getitem__</code> if, at <code>my_obj.a.b.c.d</code>, you will next proceed farther down a nonexistent tree, in which case it needs to return an object with a <code>__getitem__</code> method so you won't get an <code>AttributeError</code> thrown at you, or if you want a value, in which case it needs to return <code>None</code>.</p> <p>I would argue that in every case you have above, you should expect it to throw a <code>KeyError</code> instead of returning <code>None</code>. The reason being that you can't tell if <code>None</code> means "no key" or "someone actually stored <code>None</code> at that location". For this behavior, all you have to do is take <code>dotdictify</code>, remove <code>marker</code>, and replace <code>__getitem__</code> with:</p> <pre><code>def __getitem__(self, key): return self[key] </code></pre> <p>Because what you really want is a <code>dict</code> with <code>__getattr__</code> and <code>__setattr__</code>.</p> <p>There may be a way to remove <code>__getitem__</code> entirely and say something like <code>__getattr__ = dict.__getitem__</code>, but I think this may be over-optimization, and will be a problem if you later decide you want <code>__getitem__</code> to create the tree as it goes like <code>dotdictify</code> originally does, in which case you would change it to:</p> <pre><code>def __getitem__(self, key): if key not in self: dict.__setitem__(self, key, dotdictify()) return dict.__getitem__(self, key) </code></pre> <p>I don't like the <code>marker</code> business in the original <code>dotdictify</code>.</p> <h1>Path Support</h1> <p>The second specification (override <code>get()</code> and <code>set()</code>) is that a normal <code>dict</code> has a <code>get()</code> that operates differently from what you describe and doesn't even have a <code>set</code> (though it has a <code>setdefault()</code> which is an inverse operation to <code>get()</code>). People expect <code>get</code> to take two parameters, the second being a default if the key isn't found.</p> <p>If you want to extend <code>__getitem__</code> and <code>__setitem__</code> to handle dotted-key notation, you'll need to modify <code>doctictify</code> to:</p> <pre><code>class dotdictify(dict): def __init__(self, value=None): if value is None: pass elif isinstance(value, dict): for key in value: self.__setitem__(key, value[key]) else: raise TypeError, 'expected dict' def __setitem__(self, key, value): if '.' in key: myKey, restOfKey = key.split('.', 1) target = self.setdefault(myKey, dotdictify()) if not isinstance(target, dotdictify): raise KeyError, 'cannot set "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target)) target[restOfKey] = value else: if isinstance(value, dict) and not isinstance(value, dotdictify): value = dotdictify(value) dict.__setitem__(self, key, value) def __getitem__(self, key): if '.' not in key: return dict.__getitem__(self, key) myKey, restOfKey = key.split('.', 1) target = dict.__getitem__(self, myKey) if not isinstance(target, dotdictify): raise KeyError, 'cannot get "%s" in "%s" (%s)' % (restOfKey, myKey, repr(target)) return target[restOfKey] def __contains__(self, key): if '.' not in key: return dict.__contains__(self, key) myKey, restOfKey = key.split('.', 1) target = dict.__getitem__(self, myKey) if not isinstance(target, dotdictify): return False return restOfKey in target def setdefault(self, key, default): if key not in self: self[key] = default return self[key] __setattr__ = __setitem__ __getattr__ = __getitem__ </code></pre> <p>Test code:</p> <pre><code>&gt;&gt;&gt; life = dotdictify({'bigBang': {'stars': {'planets': {}}}}) &gt;&gt;&gt; life.bigBang.stars.planets {} &gt;&gt;&gt; life.bigBang.stars.planets.earth = { 'singleCellLife' : {} } &gt;&gt;&gt; life.bigBang.stars.planets {'earth': {'singleCellLife': {}}} &gt;&gt;&gt; life['bigBang.stars.planets.mars.landers.vikings'] = 2 &gt;&gt;&gt; life.bigBang.stars.planets.mars.landers.vikings 2 &gt;&gt;&gt; 'landers.vikings' in life.bigBang.stars.planets.mars True &gt;&gt;&gt; life.get('bigBang.stars.planets.mars.landers.spirit', True) True &gt;&gt;&gt; life.setdefault('bigBang.stars.planets.mars.landers.opportunity', True) True &gt;&gt;&gt; 'landers.opportunity' in life.bigBang.stars.planets.mars True &gt;&gt;&gt; life.bigBang.stars.planets.mars {'landers': {'opportunity': True, 'vikings': 2}} </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload