Join this PEP (with Armin's blessing) and move it forward
from where it's been sitting for a while. Update the text for Py2.7 and Py3.1. Link to a new implementation that sticks with the basic dict API and no new methods.
This commit is contained in:
parent
afa5b4cb7f
commit
2489667c4c
151
pep-0372.txt
151
pep-0372.txt
|
@ -3,11 +3,12 @@ Title: Adding an ordered dictionary to collections
|
||||||
Version: $Revision$
|
Version: $Revision$
|
||||||
Last-Modified: $Date$
|
Last-Modified: $Date$
|
||||||
Author: Armin Ronacher <armin.ronacher@active-4.com>
|
Author: Armin Ronacher <armin.ronacher@active-4.com>
|
||||||
|
Raymond Hettinger <python@rcn.com>
|
||||||
Status: Draft
|
Status: Draft
|
||||||
Type: Standards Track
|
Type: Standards Track
|
||||||
Content-Type: text/x-rst
|
Content-Type: text/x-rst
|
||||||
Created: 15-Jun-2008
|
Created: 15-Jun-2008
|
||||||
Python-Version: 2.6, 3.0
|
Python-Version: 2.7, 3.1
|
||||||
Post-History:
|
Post-History:
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,7 +74,7 @@ situations:
|
||||||
provide an ordered dictionary. [1]_
|
provide an ordered dictionary. [1]_
|
||||||
|
|
||||||
- Code ported from other programming languages such as PHP often
|
- Code ported from other programming languages such as PHP often
|
||||||
depends on a ordered dict. Having an implementation of an
|
depends on an ordered dict. Having an implementation of an
|
||||||
ordering-preserving dictionary in the standard library could ease
|
ordering-preserving dictionary in the standard library could ease
|
||||||
the transition and improve the compatibility of different libraries.
|
the transition and improve the compatibility of different libraries.
|
||||||
|
|
||||||
|
@ -82,12 +83,12 @@ Ordered Dict API
|
||||||
================
|
================
|
||||||
|
|
||||||
The ordered dict API would be mostly compatible with dict and existing
|
The ordered dict API would be mostly compatible with dict and existing
|
||||||
ordered dicts. (Note: this PEP refers to the Python 2.x dictionary
|
ordered dicts. Note: this PEP refers to the 2.7 and 3.0 dictionary
|
||||||
API; the transfer to the 3.x API is trivial.)
|
API as described in collections.Mapping abstract base class.
|
||||||
|
|
||||||
The constructor and ``update()`` both accept iterables of tuples as
|
The constructor and ``update()`` both accept iterables of tuples as
|
||||||
well as mappings like a dict does. The ordering however is preserved
|
well as mappings like a dict does. Unlike a regular dictionary,
|
||||||
for the first case:
|
the insertion order is preserved.
|
||||||
|
|
||||||
>>> d = odict([('a', 'b'), ('c', 'd')])
|
>>> d = odict([('a', 'b'), ('c', 'd')])
|
||||||
>>> d.update({'foo': 'bar'})
|
>>> d.update({'foo': 'bar'})
|
||||||
|
@ -95,11 +96,11 @@ for the first case:
|
||||||
collections.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
|
collections.odict([('a', 'b'), ('c', 'd'), ('foo', 'bar')])
|
||||||
|
|
||||||
If ordered dicts are updated from regular dicts, the ordering of new
|
If ordered dicts are updated from regular dicts, the ordering of new
|
||||||
keys is of course undefined again unless ``sort()`` is called.
|
keys is of course undefined.
|
||||||
|
|
||||||
All iteration methods as well as ``keys()``, ``values()`` and
|
All iteration methods as well as ``keys()``, ``values()`` and
|
||||||
``items()`` return the values ordered by the time the key-value pair
|
``items()`` return the values ordered by the time the key was
|
||||||
was inserted:
|
first inserted:
|
||||||
|
|
||||||
>>> d['spam'] = 'eggs'
|
>>> d['spam'] = 'eggs'
|
||||||
>>> d.keys()
|
>>> d.keys()
|
||||||
|
@ -111,80 +112,9 @@ was inserted:
|
||||||
|
|
||||||
New methods not available on dict:
|
New methods not available on dict:
|
||||||
|
|
||||||
``odict.byindex(index)``
|
|
||||||
Returns the key/value pair for an index, that is, the "position" of a key in
|
|
||||||
the ordered dict. 0 is the first key/value pair, -1 the last.
|
|
||||||
|
|
||||||
>>> d.byindex(2)
|
|
||||||
('foo', 'bar')
|
|
||||||
|
|
||||||
If there is no key for index an `IndexError` is raised. Slices are not
|
|
||||||
supported.
|
|
||||||
|
|
||||||
``odict.index(key)``
|
|
||||||
Returns the index of a key. If the key does not exist, a `ValueError` is
|
|
||||||
raised.
|
|
||||||
|
|
||||||
``odict.sort(cmp=None, key=None, reverse=False)``
|
|
||||||
Sorts the odict in place by cmp or key. This works exactly like
|
|
||||||
``list.sort()``, but the comparison functions are passed a key/value tuple,
|
|
||||||
not only the value.
|
|
||||||
|
|
||||||
>>> d = odict([(42, 1), (1, 4), (23, 7)]) d.sort() d
|
|
||||||
collections.odict([(1, 4), (23, 7), (42, 1)])
|
|
||||||
|
|
||||||
``odict.reverse()``
|
|
||||||
Reverses the odict in place.
|
|
||||||
|
|
||||||
``odict.__reversed__()``
|
``odict.__reversed__()``
|
||||||
Supports reverse iteration by key.
|
Supports reverse iteration by key.
|
||||||
|
|
||||||
``odict.__eq__()`` / ``odict.__ne__()``
|
|
||||||
Compares the odict to another object. If it's compared to another
|
|
||||||
odict the ordering of items is taken into account, otherwise only
|
|
||||||
the keys and values.
|
|
||||||
|
|
||||||
``odict.__cmp__()``
|
|
||||||
Ordered dicts are sorted by their items. ``cmp(od1, od2)`` is
|
|
||||||
equivalent to ``cmp(od1.items(), od2.items())`` if both ``od1``
|
|
||||||
and ``od2`` are ordered dicts. Otherwise the regular dict comparison
|
|
||||||
kicks in.
|
|
||||||
|
|
||||||
|
|
||||||
Python 3 Version
|
|
||||||
================
|
|
||||||
|
|
||||||
The Python 3 version of the ``odict`` returns dictionary views rather
|
|
||||||
than lists for ``odict.keys()``, ``odict.values()`` and
|
|
||||||
``odict.items()``. The keys view is equivalent to a regular keys view
|
|
||||||
but supports the following extra or changed operations:
|
|
||||||
|
|
||||||
``odict_keys.__getitem__(index)``
|
|
||||||
|
|
||||||
Returns the key for an index. This is equivalent to
|
|
||||||
``odict.byindex(index)``.
|
|
||||||
|
|
||||||
``odict_keys.index(key)``
|
|
||||||
|
|
||||||
Returns the index for a key. This exists for compatibility with
|
|
||||||
the ``Sequence`` abstract base class and is equivalent to
|
|
||||||
``odict.index(key)``.
|
|
||||||
|
|
||||||
``odict_keys.__iter__()``
|
|
||||||
|
|
||||||
Has the same semantics as ``odict.__iter__()``.
|
|
||||||
|
|
||||||
``odict_keys.__reversed__()``
|
|
||||||
|
|
||||||
Has the same semantics as ``odict.__reversed__()``.
|
|
||||||
|
|
||||||
``odict_keys.__cmp__()`` / ``odict_keys.__eq__()`` /
|
|
||||||
``odict_keys.__ne__()``
|
|
||||||
|
|
||||||
Same semantics as the equivalent ``odict`` operation. E.g.: when
|
|
||||||
compared to another odict keys view the ordering is taken into
|
|
||||||
account.
|
|
||||||
|
|
||||||
|
|
||||||
Questions and Answers
|
Questions and Answers
|
||||||
=====================
|
=====================
|
||||||
|
@ -205,7 +135,7 @@ constructor?
|
||||||
|
|
||||||
The same as for regular dicts: The latter item overrides the
|
The same as for regular dicts: The latter item overrides the
|
||||||
former. This has the side-effect that the position of the first
|
former. This has the side-effect that the position of the first
|
||||||
key is used because the key is actually overwritten:
|
key is used because only the value is actually overwritten:
|
||||||
|
|
||||||
>>> odict([('a', 1), ('b', 2), ('a', 3)])
|
>>> odict([('a', 1), ('b', 2), ('a', 3)])
|
||||||
collections.odict([('a', 3), ('b', 2)])
|
collections.odict([('a', 3), ('b', 2)])
|
||||||
|
@ -216,7 +146,7 @@ constructor?
|
||||||
Why is there no ``odict.insert()``?
|
Why is there no ``odict.insert()``?
|
||||||
|
|
||||||
There are few situations where you really want to insert a key at
|
There are few situations where you really want to insert a key at
|
||||||
an specified index. To avoid API complication, the proposed
|
a specified index. To avoid API complication, the proposed
|
||||||
solution for this situation is creating a list of items,
|
solution for this situation is creating a list of items,
|
||||||
manipulating that and converting it back into an odict:
|
manipulating that and converting it back into an odict:
|
||||||
|
|
||||||
|
@ -230,31 +160,60 @@ Is the ordered dict a dict subclass?
|
||||||
|
|
||||||
Yes. Like ``defaultdict``, ``odict`` subclasses ``dict``.
|
Yes. Like ``defaultdict``, ``odict`` subclasses ``dict``.
|
||||||
|
|
||||||
Does ``odict.pop()`` support list-like popping of items?
|
Does ``odict.popitem()`` return a particular key/value pair?
|
||||||
|
|
||||||
No. Neither ``odict.__getitem__()`` nor ``odict.pop()`` support
|
Yes. It pops-off the most recently inserted new key and its
|
||||||
retrieving or deleting items by index. Slices are not supported
|
corresponding value. This corresponds to the usual LIFO behavior
|
||||||
either. This would introduce ambiguities if integers or slice
|
exhibited by traditional push/pop pairs. It is semantically
|
||||||
objects are used as dict keys.
|
equivalent to ``k=list(od)[-1]; v=od[k]; del od[k]; return (k,v)``.
|
||||||
|
The actual implementation is more efficient. It is O(n log n)
|
||||||
|
on the first call, any successive calls are O(1).
|
||||||
|
|
||||||
|
Does odict support indexing, slicing, and whatnot?
|
||||||
|
|
||||||
As a matter of fact, ``odict`` does not implement the ``Sequence``
|
As a matter of fact, ``odict`` does not implement the ``Sequence``
|
||||||
interface.
|
interface. Rather, it is a ``MutableMapping`` that remembers
|
||||||
|
the order of key insertion. The only sequence-like addition is
|
||||||
|
automatic support for ``reversed``.
|
||||||
|
|
||||||
|
Does odict support alternate sort orders such as alphabetical?
|
||||||
|
|
||||||
Example Implementation
|
No. Those wanting different sort orders really need to be using another
|
||||||
======================
|
technique. The odict is all about recording insertion order. If any
|
||||||
|
other order is of interest, then another structure (like an in-memory
|
||||||
|
dbm) is likely a better fit. It would be a mistake to try to be all
|
||||||
|
things to all users.
|
||||||
|
|
||||||
A poorly performing example implementation of the odict written in
|
Reference Implementation
|
||||||
Python is available:
|
========================
|
||||||
|
|
||||||
`odict.py <http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py>`_
|
A proposed version is at:
|
||||||
|
|
||||||
The version for ``collections`` should be implemented in C and use a
|
`OrderedDict recipe <http://code.activestate.com/recipes/576669/>`_
|
||||||
linked list internally.
|
|
||||||
|
The proposed version has several merits:
|
||||||
|
|
||||||
|
* Strict compliance with the MutableMapping API and no new methods
|
||||||
|
so that the learning curve is near zero. It is simply a dictionary
|
||||||
|
that remembers insertion order.
|
||||||
|
|
||||||
|
* Generally good performance. The big-oh times are the same as regular
|
||||||
|
dictionaries except for the cost of a single sort prior to the
|
||||||
|
first ordered retrieval (via *__iter__* or somesuch).
|
||||||
|
|
||||||
|
* Key insertion and deletion is O(1). The work of organizing keys into
|
||||||
|
correct order is deferred to the end (instead of trying to maintain
|
||||||
|
sorted list, linked list, or btree as the dict is built-up). This
|
||||||
|
corresponds to typical use patterns (read-in ordered key/value pairs,
|
||||||
|
make modifications, and then write them back out in insertion order)
|
||||||
|
and it takes advantage of Python's highly efficient built-in sort.
|
||||||
|
|
||||||
|
* The code runs without modification on Py2.6, Py2.7, Py3.0, and Py3.1.
|
||||||
|
|
||||||
Other implementations of ordered dicts in various Python projects or
|
Other implementations of ordered dicts in various Python projects or
|
||||||
standalone libraries, that inspired the API proposed here, are:
|
standalone libraries, that inspired the API proposed here, are:
|
||||||
|
|
||||||
|
- `odict in Python`_
|
||||||
- `odict in Babel`_
|
- `odict in Babel`_
|
||||||
- `OrderedDict in Django`_
|
- `OrderedDict in Django`_
|
||||||
- `The odict module`_
|
- `The odict module`_
|
||||||
|
@ -262,7 +221,7 @@ standalone libraries, that inspired the API proposed here, are:
|
||||||
- `StableDict`_
|
- `StableDict`_
|
||||||
- `Armin Rigo's OrderedDict`_
|
- `Armin Rigo's OrderedDict`_
|
||||||
|
|
||||||
|
.. _odict in Python: http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py
|
||||||
.. _odict in Babel: http://babel.edgewall.org/browser/trunk/babel/util.py?rev=374#L178
|
.. _odict in Babel: http://babel.edgewall.org/browser/trunk/babel/util.py?rev=374#L178
|
||||||
.. _OrderedDict in Django:
|
.. _OrderedDict in Django:
|
||||||
http://code.djangoproject.com/browser/django/trunk/django/utils/datastructures.py?rev=7140#L53
|
http://code.djangoproject.com/browser/django/trunk/django/utils/datastructures.py?rev=7140#L53
|
||||||
|
|
Loading…
Reference in New Issue