aboutsummaryrefslogtreecommitdiffstats
path: root/odict.py
diff options
context:
space:
mode:
Diffstat (limited to 'odict.py')
-rw-r--r--odict.py328
1 files changed, 0 insertions, 328 deletions
diff --git a/odict.py b/odict.py
deleted file mode 100644
index 3bf708a..0000000
--- a/odict.py
+++ /dev/null
@@ -1,328 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
- odict
- ~~~~~
-
- This module is an example implementation of an ordered dict for the
- collections module. It's not written for performance (it actually
- performs pretty bad) but to show how the API works.
-
-
- Questions and Answers
- =====================
-
- Why would anyone need ordered dicts?
-
- Dicts in python are unordered which means that the order of items when
- iterating over dicts is undefined. As a matter of fact it is most of
- the time useless and differs from implementation to implementation.
-
- Many developers stumble upon that problem sooner or later when
- comparing the output of doctests which often does not match the order
- the developer thought it would.
-
- Also XML systems such as Genshi have their problems with unordered
- dicts as the input and output ordering of tag attributes is often
- mixed up because the ordering is lost when converting the data into
- a dict. Switching to lists is often not possible because the
- complexity of a lookup is too high.
-
- Another very common case is metaprogramming. The default namespace
- of a class in python is a dict. With Python 3 it becomes possible
- to replace it with a different object which could be an ordered dict.
- Django is already doing something similar with a hack that assigns
- numbers to some descriptors initialized in the class body of a
- specific subclass to restore the ordering after class creation.
-
- When porting code from programming languages such as PHP and Ruby
- where the item-order in a dict is guaranteed it's also a great help
- to have an equivalent data structure in Python to ease the transition.
-
- Where are new keys added?
-
- At the end. This behavior is consistent with Ruby 1.9 Hashmaps
- and PHP Arrays. It also matches what common ordered dict
- implementations do currently.
-
- What happens if an existing key is reassigned?
-
- The key is *not* moved. This is consitent with existing
- implementations and can be changed by a subclass very easily::
-
- class movingodict(odict):
- def __setitem__(self, key, value):
- self.pop(key, None)
- odict.__setitem__(self, key, value)
-
- Moving keys to the end of a ordered dict on reassignment is not
- very useful for most applications.
-
- Does it mean the dict keys are sorted by a sort expression?
-
- That's not the case. The odict only guarantees that there is an order
- and that newly inserted keys are inserted at the end of the dict. If
- you want to sort it you can do so, but newly added keys are again added
- at the end of the dict.
-
- I initializes the odict with a dict literal but the keys are not
- ordered like they should!
-
- Dict literals in Python generate dict objects and as such the order of
- their items is not guaranteed. Before they are passed to the odict
- constructor they are already unordered.
-
- What happens if keys appear multiple times in the list passed to the
- constructor?
-
- The same as for the dict. The latter item overrides the former. This
- has the side-effect that the position of the first key is used because
- the key is actually overwritten:
-
- >>> odict([('a', 1), ('b', 2), ('a', 3)])
- odict.odict([('a', 3), ('b', 2)])
-
- This behavor is consistent with existing implementation in Python
- and the PHP array and the hashmap in Ruby 1.9.
-
- This odict doesn't scale!
-
- Yes it doesn't. The delitem operation is O(n). This is file is a
- mockup of a real odict that could be implemented for collections
- based on an linked list.
-
- Why is there no .insert()?
-
- There are few situations where you really want to insert a key at
- an specified index. To now make the API too complex the proposed
- solution for this situation is creating a list of items, manipulating
- that and converting it back into an odict:
-
- >>> d = odict([('a', 42), ('b', 23), ('c', 19)])
- >>> l = d.items()
- >>> l.insert(1, ('x', 0))
- >>> odict(l)
- odict.odict([('a', 42), ('x', 0), ('b', 23), ('c', 19)])
-
- :copyright: (c) 2008 by Armin Ronacher and PEP 273 authors.
- :license: modified BSD license.
-"""
-from itertools import izip, imap
-from copy import deepcopy
-
-missing = object()
-
-
-class odict(dict):
- """
- Ordered dict example implementation.
-
- This is the proposed interface for a an ordered dict as proposed on the
- Python mailinglist (proposal_).
-
- It's a dict subclass and provides some list functions. The implementation
- of this class is inspired by the implementation of Babel but incorporates
- some ideas from the `ordereddict`_ and Django's ordered dict.
-
- The constructor and `update()` both accept iterables of tuples as well as
- mappings:
-
- >>> d = odict([('a', 'b'), ('c', 'd')])
- >>> d.update({'foo': 'bar'})
- >>> d
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
-
- Keep in mind that when updating from dict-literals the order is not
- preserved as these dicts are unsorted!
-
- You can copy an odict like a dict by using the constructor, `copy.copy`
- or the `copy` method and make deep copies with `copy.deepcopy`:
-
- >>> from copy import copy, deepcopy
- >>> copy(d)
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
- >>> d.copy()
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
- >>> odict(d)
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
- >>> d['spam'] = []
- >>> d2 = deepcopy(d)
- >>> d2['spam'].append('eggs')
- >>> d
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
- >>> d2
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', ['eggs'])])
-
- All iteration methods as well as `keys`, `values` and `items` return
- the values ordered by the the time the key-value pair is inserted:
-
- >>> d.keys()
- ['a', 'c', 'foo', 'spam']
- >>> d.values()
- ['b', 'd', 'bar', []]
- >>> d.items()
- [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
- >>> list(d.iterkeys())
- ['a', 'c', 'foo', 'spam']
- >>> list(d.itervalues())
- ['b', 'd', 'bar', []]
- >>> list(d.iteritems())
- [('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])]
-
- Index based lookup is supported too by `byindex` which returns the
- key/value pair for an index:
-
- >>> d.byindex(2)
- ('foo', 'bar')
-
- You can reverse the odict as well:
-
- >>> d.reverse()
- >>> d
- odict.odict([('spam', []), ('foo', 'bar'), ('c', 'd'), ('a', 'b')])
-
- And sort it like a list:
-
- >>> d.sort(key=lambda x: x[0].lower())
- >>> d
- odict.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar'), ('spam', [])])
-
- .. _proposal: http://thread.gmane.org/gmane.comp.python.devel/95316
- .. _ordereddict: http://www.xs4all.nl/~anthon/Python/ordereddict/
- """
-
- def __init__(self, *args, **kwargs):
- dict.__init__(self)
- self._keys = []
- self.update(*args, **kwargs)
-
- def __delitem__(self, key):
- dict.__delitem__(self, key)
- self._keys.remove(key)
-
- def __setitem__(self, key, item):
- if key not in self:
- self._keys.append(key)
- dict.__setitem__(self, key, item)
-
- def __deepcopy__(self, memo=None):
- if memo is None:
- memo = {}
- d = memo.get(id(self), missing)
- if d is not missing:
- return d
- memo[id(self)] = d = self.__class__()
- dict.__init__(d, deepcopy(self.items(), memo))
- d._keys = self._keys[:]
- return d
-
- def __getstate__(self):
- return {'items': dict(self), 'keys': self._keys}
-
- def __setstate__(self, d):
- self._keys = d['keys']
- dict.update(d['items'])
-
- def __reversed__(self):
- return reversed(self._keys)
-
- def __eq__(self, other):
- if isinstance(other, odict):
- if not dict.__eq__(self, other):
- return False
- return self.items() == other.items()
- return dict.__eq__(self, other)
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
- def __cmp__(self, other):
- if isinstance(other, odict):
- return cmp(self.items(), other.items())
- elif isinstance(other, dict):
- return dict.__cmp__(self, other)
- return NotImplemented
-
- @classmethod
- def fromkeys(cls, iterable, default=None):
- return cls((key, default) for key in iterable)
-
- def clear(self):
- del self._keys[:]
- dict.clear(self)
-
- def copy(self):
- return self.__class__(self)
-
- def items(self):
- return zip(self._keys, self.values())
-
- def iteritems(self):
- return izip(self._keys, self.itervalues())
-
- def keys(self):
- return self._keys[:]
-
- def iterkeys(self):
- return iter(self._keys)
-
- def pop(self, key, default=missing):
- if default is missing:
- return dict.pop(self, key)
- elif key not in self:
- return default
- self._keys.remove(key)
- return dict.pop(self, key, default)
-
- def popitem(self, key):
- self._keys.remove(key)
- return dict.popitem(key)
-
- def setdefault(self, key, default=None):
- if key not in self:
- self._keys.append(key)
- dict.setdefault(self, key, default)
-
- def update(self, *args, **kwargs):
- sources = []
- if len(args) == 1:
- if hasattr(args[0], 'iteritems'):
- sources.append(args[0].iteritems())
- else:
- sources.append(iter(args[0]))
- elif args:
- raise TypeError('expected at most one positional argument')
- if kwargs:
- sources.append(kwargs.iteritems())
- for iterable in sources:
- for key, val in iterable:
- self[key] = val
-
- def values(self):
- return map(self.get, self._keys)
-
- def itervalues(self):
- return imap(self.get, self._keys)
-
- def index(self, item):
- return self._keys.index(item)
-
- def byindex(self, item):
- key = self._keys[item]
- return (key, dict.__getitem__(self, key))
-
- def reverse(self):
- self._keys.reverse()
-
- def sort(self, *args, **kwargs):
- self._keys.sort(*args, **kwargs)
-
- def __repr__(self):
- return 'odict.odict(%r)' % self.items()
-
- __copy__ = copy
- __iter__ = iterkeys
-
-
-if __name__ == '__main__':
- import doctest
- doctest.testmod()