patternpythonMinor
Container (dict preserving key order, with attribute access)
Viewed 0 times
dictordercontainerwithpreservingattributeaccesskey
Problem
This class is part of the Construct library.
I know, this is similar to
For anyone preferring more interactive code, you can pull the trunk and run
```
def recursion_lock(retval="", lock_name="__recursion_lock__"):
def decorator(func):
def wrapper(self, *args, **kw):
if getattr(self, lock_name, False):
return retval
setattr(self, lock_name, True)
try:
return func(self, *args, **kw)
finally:
delattr(self, lock_name)
wrapper.__name__ = func.__name__
return wrapper
return decorator
class Container(dict):
r"""
A generic container of attributes.
Containers are dictionaries, translating attribute access into key access, and preserving key order.
"""
__slots__ = ["__keys_order__","__recursion_lock__"]
def __init__(self, *args, **kw):
object.__setattr__(self, "__keys_order__", [])
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setitem__(self, key, val):
if key not in self:
self.__keys_order__.append(key)
dict.__setitem__(self, key, val)
def __delitem__(self, key):
dict.__delitem__(self, key)
self.__keys_order__.remove(key)
__delattr__ = __delitem__
__setattr__ = __setitem__
def __call__(self, **kw):
for k,v in kw.items():
self.__setitem__(k, v)
return self
def clear(self):
dict.clear(self)
del self.__keys_order__[:]
def pop(self, key, *default):
Container is a dict that:- turns attribute access into key access so
con.a -> con["a"]
- preserves the order of keys
I know, this is similar to
collections.OrderedDict but it isn't available in Python 2.6.For anyone preferring more interactive code, you can pull the trunk and run
nosetests.```
def recursion_lock(retval="", lock_name="__recursion_lock__"):
def decorator(func):
def wrapper(self, *args, **kw):
if getattr(self, lock_name, False):
return retval
setattr(self, lock_name, True)
try:
return func(self, *args, **kw)
finally:
delattr(self, lock_name)
wrapper.__name__ = func.__name__
return wrapper
return decorator
class Container(dict):
r"""
A generic container of attributes.
Containers are dictionaries, translating attribute access into key access, and preserving key order.
"""
__slots__ = ["__keys_order__","__recursion_lock__"]
def __init__(self, *args, **kw):
object.__setattr__(self, "__keys_order__", [])
for arg in args:
for k, v in arg.items():
self[k] = v
for k, v in kw.items():
self[k] = v
def __getattr__(self, name):
try:
return self[name]
except KeyError:
raise AttributeError(name)
def __setitem__(self, key, val):
if key not in self:
self.__keys_order__.append(key)
dict.__setitem__(self, key, val)
def __delitem__(self, key):
dict.__delitem__(self, key)
self.__keys_order__.remove(key)
__delattr__ = __delitem__
__setattr__ = __setitem__
def __call__(self, **kw):
for k,v in kw.items():
self.__setitem__(k, v)
return self
def clear(self):
dict.clear(self)
del self.__keys_order__[:]
def pop(self, key, *default):
Solution
While the performance of your container seems to be not your major concern, it is always nice to know where you are standing. Therefore I would compare it to this implementation of an OrderedDict for
In addition, what your container seems to lack is to give it an ordered sequence on initialization. It can only take an iterable which has the method
you loose the order, because of python dicts disorderedness. You try to get around this with
but in my opinion this is less clean and clear than using e.g. a list of tuples. The latter also simplifies your
The implementation for this is actually already in your
I also added
In your
It is better to ask forgiveness than permission, so I would use
I would also think about which you want to put in the
This takes advantage of the fact that
Your
2.4 <= python <= 2.6.In addition, what your container seems to lack is to give it an ordered sequence on initialization. It can only take an iterable which has the method
items defined. But it would be nice to be able to give it an iterable of 2-tuples, so something like:c = Container([("a", 1), ("b", 2), ("c", 3), ...])collections.OrderedDict offers this, therefore a user might expect your implementation to also have it. Without it, the ordering will not give you any advantage, because as soon as you do:c = Container({"a": 1, "b": 2, "c": 3, ...})
c = Container(a=1, b=2, c=3, ...)you loose the order, because of python dicts disorderedness. You try to get around this with
__call__, allowing you to do:c = Container(a=1)(b=2)(c=3)but in my opinion this is less clean and clear than using e.g. a list of tuples. The latter also simplifies your
copy:def copy(self):
return inst = self.__class__(self.iteritems())
__copy__ = copyThe implementation for this is actually already in your
update method.I also added
__copy__ = copy here to allow usage of the copy module.In your
_search method, you are doing a return None at the end. This is superfluous as that is the default return value of a python function.It is better to ask forgiveness than permission, so I would use
try..except in update:def update(self, seq, **kw):
try:
for k in seq.keys():
self[k] = seq[k]
except AttributeError:
for k, v in seq:
self[k] = v
dict.update(self, kw)I would also think about which you want to put in the
try clause (the one that fails less often, which depends on you usage of this class).dict.pop() (without a key) is not defined, because the 'last' element in a dict is of course not well-defined. For an ordered dict, it might make sense, however, to allow it (and collections.OrderedDict does, with its pop method):def pop(self, key=None, *default):
if not key:
key = self.__keys_order__.pop()
else:
self.__keys_order__.remove(key)
val = dict.pop(self, key, *default)
return valThis takes advantage of the fact that
list.pop is O(1), whereas list.remove is O(n) if the last element is the one you want to remove (because it needs to search through the whole list to find the last element).Your
popitem also seems to be randomly ordered, because it directly uses dict.popitem. It should be:def popitem(self):
k = self.__keys_order__.pop()
v = dict.pop(self, k)
return k, vCode Snippets
c = Container([("a", 1), ("b", 2), ("c", 3), ...])c = Container({"a": 1, "b": 2, "c": 3, ...})
c = Container(a=1, b=2, c=3, ...)c = Container(a=1)(b=2)(c=3)def copy(self):
return inst = self.__class__(self.iteritems())
__copy__ = copydef update(self, seq, **kw):
try:
for k in seq.keys():
self[k] = seq[k]
except AttributeError:
for k, v in seq:
self[k] = v
dict.update(self, kw)Context
StackExchange Code Review Q#140500, answer score: 4
Revisions (0)
No revisions yet.