Comment

K Lars Lohn

the class that originally inspired this posting was the configman/socorro DotDict class. It is a class meant to be a derivative of dict. It was originally written like this:

....class DotDict(dict):
........__getattr__ = dict.__getitem__
........__setattr__ = dict.__setitem__
........__delattr__ = dict.__delitem__

This gave the result of a mapping that happened to have a convenient dot notation for accessing the values. Essentially, it hijacks the attribute notation for use in accessing items. This is fraught with peril as the deep copy conundrum demonstrates. Overriding the __getattr__ and __setattr__ functions always seems cause trouble and confusion.

While the original implementation was expedient, I suggest that we may want to look at the problem from the other direction. Let's override the __getitem__ and __setitem__ methods instead. That seems to be less perilous if not a bit more verbose:

....class DotDict(collections.MutableMapping):
........def __init__(self, initializer=None):
............if isinstance(initializer, collections.Mapping):
................self.__dict__.update(initializer)
............elif initializer is not None:
................raise TypeError('can only initialize with a Mapping')
........def __getitem__(self, key):
............return self.__dict__[key]
........def __setitem__(self, key, value):
............self.__dict__[key] = value
........def __delitem__(self, key):
............del self.__dict__[key]
........def __iter__(self):
............return ((k, v) for k, v in self.__dict__.iteritems()
....................if not k.startswith('_'))
........def __len__(self):
............return len(self.__dict__)

With this implementation, there is no interference with other magic methods. Deep copy works fine without having to override it with a local implementation.

This implementation will raise an AttributeError rather than a KeyError if a key is not found. This is somewhat of an irreconcilable difference. For use in configman/socorro, we're more interested in following the collections.Mapping interface, so we ought to have a KeyError rather than an AttributeError. Adding this method ought to fix that up:

........def __getattr__(self, key):
............try:
................return super(DotDict, self).__getattr__(key)
............except AttributeError:
................if not key.startswith('_'):
....................raise KeyError(key)
................raise