AttrDict/DefAttrDict rewrite/bugfixing
The logic for both of these has been corrected; neither will try to handle reserved or preexisting attributes as though they should be part of their dicts. These can still be set manually, but it makes things much safer. New attributes have to come from the class or local __dict__ modifications.
This commit is contained in:
parent
b04e2e8ba0
commit
22c5354bc6
1 changed files with 99 additions and 33 deletions
|
@ -1,67 +1,133 @@
|
||||||
# Modified version of the code featured at the given link
|
# Heavily modified version of the code featured at the given link
|
||||||
## {{{ http://code.activestate.com/recipes/473786/ (r1)
|
## {{{ http://code.activestate.com/recipes/473786/ (r1)
|
||||||
class AttrDict(dict):
|
class AttrDict(dict):
|
||||||
"""A dictionary with attribute-style access. It maps attribute access to
|
"""A dictionary with attribute-style access. It maps attribute access to
|
||||||
the real dictionary."""
|
the real dictionary.
|
||||||
def __init__(self, init={}): super(AttrDict, self).__init__(init)
|
|
||||||
def __getstate__(self): return self.__dict__.items()
|
Note that accesses to preexisting (e.g. class inherited) or reserved
|
||||||
|
attributes are handled as they would be normally, and will not be
|
||||||
|
overwritten.
|
||||||
|
Overload _is_reserved if you want to change this."""
|
||||||
|
def __init__(self, init={}):
|
||||||
|
super(AttrDict, self).__init__(init)
|
||||||
|
|
||||||
|
def __getstate__(self):
|
||||||
|
return self.__dict__.items()
|
||||||
def __setstate__(self, items):
|
def __setstate__(self, items):
|
||||||
for key, val in items: self.__dict__[key] = val
|
for key, val in items: self.__dict__[key] = val
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{}({})".format(
|
return "{0}({1})".format(
|
||||||
type(self).__name__,
|
type(self).__name__,
|
||||||
super(AttrDict, self).__repr__()
|
super(AttrDict, self).__repr__()
|
||||||
)
|
)
|
||||||
def __setitem__(self, key, value):
|
|
||||||
return super(AttrDict, self).__setitem__(key, value)
|
def __setitem__(self, name, value):
|
||||||
|
return super(AttrDict, self).__setitem__(name, value)
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
return super(AttrDict, self).__getitem__(name)
|
return super(AttrDict, self).__getitem__(name)
|
||||||
def __delitem__(self, name):
|
def __delitem__(self, name):
|
||||||
return super(AttrDict, self).__delitem__(name)
|
return super(AttrDict, self).__delitem__(name)
|
||||||
|
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
# Basically, fall back on __getitem__ first
|
# NOTE: __getattr__ is called if the code has already failed to access
|
||||||
# Try to access ourselves as a dict. Failing that, check for attributes
|
# an attribute on this object. The rest of this code reflects this.
|
||||||
# with the same name. Failing *that*, throw AttributeError to keep
|
# We could override __getattribute__ to bypass this, but that's not
|
||||||
# other code happy.
|
# worthwhile.
|
||||||
try: retval = self[name]
|
|
||||||
except KeyError as err:
|
# We don't do any special handling for reserved names.
|
||||||
try: retval = self.__dict__[name]
|
if self._is_reserved(name):
|
||||||
except KeyError:
|
# Fall back to normal handling, by force.
|
||||||
# Raising KeyError here will confuse __deepcopy__, so don't do
|
return object.__getattribute__(self, name)
|
||||||
# that.
|
|
||||||
raise AttributeError("No key/attr {!r}".format(name))
|
try:
|
||||||
return retval
|
# Try to __getitem__.
|
||||||
__setattr__ = __setitem__
|
result = self[name]
|
||||||
|
except (KeyError, AttributeError):
|
||||||
|
# Raising KeyError here will confuse __deepcopy__, so don't do
|
||||||
|
# that.
|
||||||
|
# Throw a custom error.
|
||||||
|
raise AttributeError("No key/attr {0!r}".format(name))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def __setattr__(self, name, value):
|
||||||
|
# Set to the attribute first if it's defined.
|
||||||
|
# (NOTE: This isn't subject to the same checking as __getattr__.)
|
||||||
|
# Using dir() also checks non-instance definitions, so things defined
|
||||||
|
# on a class can be easily set on an instance this way.
|
||||||
|
|
||||||
|
# Detect special/reserved names.
|
||||||
|
if name in dir(self) or self._is_reserved(name):
|
||||||
|
# Set it if it's 'special' because we aren't supposed to touch any
|
||||||
|
# of that - too many potential implementation issues.
|
||||||
|
#
|
||||||
|
# Apparently we're also not supposed to set our own dict directly
|
||||||
|
# in this particular function?...
|
||||||
|
return object.__setattr__(self, name, value)
|
||||||
|
else:
|
||||||
|
return super(AttrDict, self).__setitem__(name, value)
|
||||||
|
|
||||||
def __delattr__(self, name):
|
def __delattr__(self, name):
|
||||||
try: del self[name]
|
# We very *specifically* use self.__dict__ here, because we couldn't
|
||||||
except KeyError as err:
|
# possibly delete a value that doesn't yet *exist* in our instance's
|
||||||
try: del self.__dict__[name]
|
# namespace yet!
|
||||||
except KeyError:
|
# This shouldn't be a problem, since this type should never have
|
||||||
|
# __slots__.
|
||||||
|
if name in self.__dict__:
|
||||||
|
# See __setattr__.
|
||||||
|
return object.__delattr__(self, name)
|
||||||
|
else:
|
||||||
|
try: del self[name]
|
||||||
|
except KeyError as err:
|
||||||
raise AttributeError(str(err))
|
raise AttributeError(str(err))
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _is_reserved(name):
|
||||||
|
"""Check if an attribute name is reserved for system use."""
|
||||||
|
# A very simple method.
|
||||||
|
result = name[:2] == name[-2:] == '__'
|
||||||
|
return result
|
||||||
|
|
||||||
def copy(self): return type(self)(self)
|
def copy(self): return type(self)(self)
|
||||||
__copy__ = copy
|
__copy__ = copy
|
||||||
## end of http://code.activestate.com/recipes/473786/ }}}
|
## end of http://code.activestate.com/recipes/473786/ }}}
|
||||||
|
|
||||||
class DefAttrDict(AttrDict):
|
class DefAttrDict(AttrDict):
|
||||||
|
default_factory = None
|
||||||
|
|
||||||
def __init__(self, default_factory=None, *args, **kwargs):
|
def __init__(self, default_factory=None, *args, **kwargs):
|
||||||
self.__dict__["default_factory"] = default_factory
|
self.default_factory = default_factory
|
||||||
super(DefAttrDict, self).__init__(*args, **kwargs)
|
super(DefAttrDict, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "%s(%r, %s)" % (
|
return "{0}({1!r}, {2})".format(
|
||||||
type(self).__name__,
|
type(self).__name__,
|
||||||
self.default_factory,
|
self.default_factory,
|
||||||
super(AttrDict, self).__repr__()
|
# We skip normal processing here, since AttrDict provides basic
|
||||||
)
|
# repr for classes in general, which we don't want.
|
||||||
|
dict.__repr__(self)
|
||||||
|
)
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
try:
|
try:
|
||||||
return super(DefAttrDict, self).__getitem__(name)
|
result = super(DefAttrDict, self).__getitem__(name)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
##if self.default_factory is None: return None
|
|
||||||
##return self.default_factory()
|
|
||||||
result = None
|
result = None
|
||||||
if self.default_factory is not None:
|
if self.default_factory is not None:
|
||||||
result = self.default_factory()
|
result = self.default_factory()
|
||||||
self[name] = result
|
self[name] = result
|
||||||
return result
|
return result
|
||||||
__getattr__ = __getitem__
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
try:
|
||||||
|
result = super(DefAttrDict, self).__getattr__(name)
|
||||||
|
except AttributeError:
|
||||||
|
# Detect special/reserved names.
|
||||||
|
if self._is_reserved(name):
|
||||||
|
# We shouldn't automatically fill these in.
|
||||||
|
# Pass this along.
|
||||||
|
raise
|
||||||
|
return result
|
||||||
|
|
||||||
def copy(self): return type(self)(self.default_factory, self)
|
def copy(self): return type(self)(self.default_factory, self)
|
||||||
|
__copy__ = copy
|
||||||
|
|
Loading…
Reference in a new issue