ANNOUNCE: Thesaurus - a recursive dictionary subclass using attributes
Steven D'Aprano
steve+comp.lang.python at pearwood.info
Tue Dec 11 03:12:19 EST 2012
On Mon, 10 Dec 2012 22:48:50 -0500, Dave Cinege wrote:
> Thesaurus: A different way to call a dictionary.
Is this intended as a ready-for-production class?
> Thesaurus is a new a dictionary subclass which allows calling keys as if
> they are class attributes and will search through nested objects
> recursively when __getitem__ is called.
If only that were true...
py> d = Thesaurus()
py> d['spam'] = {}
py> d['spam']['ham'] = 'cheese' # key access works
py> d.spam.ham # but attribute access doesn't
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'dict' object has no attribute 'ham'
> You will notice that the code is disgusting simple. However I have found
> that this has completely changed the way I program in python.
By making it less robust and more prone to errors?
py> d = Thesaurus()
py> d.copy = "some value"
py> assert d.copy == "some value"
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
Operations which silently fail or do the wrong thing are not a good
thing. Scarily, this isn't even consistent:
py> "%(copy)s" % d
'some access'
py> d.copy
<built-in method copy of Thesaurus object at 0xb717a65c>
Some further comments about your code:
> class Thesaurus (dict):
> [...]
> def __getitem__(self, key):
> if '.' not in key:
> return dict.__getitem__(self, key)
> l = key.split('.')
> if isinstance(l[0], (dict, Thesaurus)):
> a = self.data
Testing for isinstance (dict, Thesaurus) is redundant, since Thesaurus is
a subclass of dict.
More importantly, there are no circumstances where l[0] will be a dict.
Since l is created by splitting a string, l[0] must be a string and this
clause is dead code.
Which is good, because self.data is not defined anywhere, so if you ever
did reach this branch, your code would fail.
> else:
> a = self
> for i in range(len(l)): # Walk keys recursivly
This is usually better written as:
for subkey in l:
# look up subkey
# or if all else fails
raise KeyError(subkey)
KeyError('spam.ham.cheese [1]') is misleading, since it implies to me
that looking up spam.ham.cheese succeeded, and the failed lookup was on
1. I would expect either of these:
KeyError('spam.ham')
KeyErroor('ham')
with a slight preference for the first.
Further issues with your code:
> try:
> a = a[l[i]] # Attempt key
> except:
Bare excepts like this are not good. At best they are lazy and sloppy,
good only for throw-away code. At worst, they are hide bugs. In this
case, they can hide bugs:
py> class X(object):
... @property
... def test(self):
... return 1/0 # oops a bug
...
py> d = Thesaurus(a=X())
py> d.a.test # Gives the correct exception for the error.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 4, in test
ZeroDivisionError: float division
py>
py> "%(a.test)s" % d # Lies about the problem.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 29, in __getitem__
KeyError: 'a.test [1]'
One more comment:
> # I like to create a master global object called 'g'.
> # No more 'global' statements for me.
> g = thes()
This is not a good thing. Encouraging the use of globals is a bad thing.
--
Steven
More information about the Python-list
mailing list