sorteddict PEP proposal [started off as orderedict]

Mark Summerfield m.n.summerfield at
Wed Sep 26 08:59:41 CEST 2007

On 25 Sep, 22:33, Paul Hankin <paul.han... at> wrote:
> On Sep 25, 9:55 pm, Mark Summerfield <m.n.summerfi... at>
> wrote:
> > ...
> > class sorteddict(dict):
> > ...
> >         if self.__keys is None:
> >             self.__keys = sorted(dict.keys(self), cmp=self.__cmp,
> >                                  key=self.__key,
> > reverse=self.__reverse)
> You'd be better defining __keys like this:
> def __getkeys(self):
>   if self.__keycache is None:
>      self.__keycache = dict.keys(self)
>      self.__keycache.sort(cmp=...)
>   return self.__keycache
> __keys = property(__getkeys)
> and where you have 'self.__keys = None', either replace with
> 'self.__keycache = None' or more cleanly with a call to:
> def __invalidate_key_cache(self):
>     self.__keycache = None
> to improve the code quality.

Yes, that's much better.

As you've no doubt realised, this particular implementation gives best
performance when the pattern of use is: lots of edits, lots of
lookups, ..., and gives worst performance when the pattern of use is:
edit, lookup, edit, lookup (in which case using a dict and sorted() is
probably better).

So there is lots of scope for someone to do a version that has good
performance for all patterns of use:-)

I've put a full version with doctests & docstrings on PyPI:

Here's the stripped version (just 92 lines):

class sorteddict(dict):

    def __init__(self, iterable=None, cmp=None, key=None,
        if iterable is None:
            iterable = []
        dict.__init__(self, iterable)
        self.__cmp = cmp
        self.__key = key
        self.__reverse = reverse
        self.__keycache = None

    def __keys(self):
        if self.__keycache is None:
            self.__keycache = dict.keys(self)
            self.__keycache.sort(cmp=self.__cmp, key=self.__key,
        return self.__keycache

    def __invalidate_key_cache(self):
        self.__keycache = None

    def update(self, *args, **kwargs):
        dict.update(self, *args, **kwargs)

    def fromkeys(cls, iterable, value=None):
        dictionary = cls()
        for key in iterable:
            dictionary[key] = value
        return dictionary

    def copy(self):
        return sorteddict(dict.copy(self), cmp=self.__cmp,
                          key=self.__key, reverse=self.__reverse)

    def clear(self):

    def setdefault(self, key, value):
        return dict.setdefault(self, key, value)

    def pop(self, key, value=None):
        if key not in self:
            return value
        return dict.pop(self, key, value)

    def popitem(self):
        return dict.popitem(self)

    def keys(self):
        return self.__keys[:]

    def values(self):
        return [self[key] for key in self.__keys]

    def items(self):
        return [(key, self[key]) for key in self.__keys]

    def __iter__(self):
        return iter(self.__keys)

    def iterkeys(self):
        return iter(self.__keys)

    def itervalues(self):
        for key in self.__keys:
            yield self[key]

    def iteritems(self):
        for key in self.__keys:
            yield key, self[key]

    def __delitem__(self, key):
        dict.__delitem__(self, key)

    def __setitem__(self, key, value):
        dict.__setitem__(self, key, value)

    def __repr__(self):
        raise NotImplementedError()

    def __str__(self):
        return "{%s}" % ", ".join(
               ["%r: %r" % (key, self[key]) for key in self.__keys])

More information about the Python-list mailing list