Update the reverse iteration pep to reflect comments from comp.lang.python,

from Guido, and from some on python-dev.
This commit is contained in:
Raymond Hettinger 2003-10-28 10:16:32 +00:00
parent eefc4df37c
commit 26733836c2
2 changed files with 50 additions and 86 deletions

View File

@ -118,7 +118,7 @@ Index by Category
S 318 Function/Method Decorator Syntax Smith
S 319 Python Synchronize/Asynchronize Block Pelletier
S 321 Date/Time Parsing and Formatting Kuchling
S 322 Reverse Iteration Methods Hettinger
S 322 Reverse Iteration Hettinger
S 323 Copyable Iterators Martelli
S 754 IEEE 754 Floating Point Special Values Warnes
@ -337,7 +337,7 @@ Numerical Index
S 319 Python Synchronize/Asynchronize Block Pelletier
I 320 Python 2.4 Release Schedule Warsaw
S 321 Date/Time Parsing and Formatting Kuchling
S 322 Reverse Iteration Methods Hettinger
S 322 Reverse Iteration Hettinger
SR 666 Reject Foolish Indentation Creighton
S 754 IEEE 754 Floating Point Special Values Warnes

View File

@ -1,5 +1,5 @@
PEP: 322
Title: Reverse Iteration Methods
Title: Reverse Iteration
Version: $Revision$
Last-Modified: $Date$
Author: Raymond Hettinger <python@rcn.com>
@ -14,8 +14,8 @@ Post-History: 24-Sep-2003
Abstract
========
This proposal is to extend the API of several sequence types
to include a method for iterating over the sequence in reverse.
This proposal is to add a builtin function to support reverse
iteration over sequences.
Motivation
@ -46,47 +46,58 @@ does arise regularly in practice. See `Real World Use Cases`_ below.
Proposal
========
Add a method called *iterreverse()* to sequence objects that can
benefit from it. The above examples then simplify to::
Add a builtin function called *inreverse()* that makes a reverse
iterator over sequence objects that support __getitem__() and
__len__().
for i in xrange(n).iterreverse():
The above examples then simplify to::
for i in inreverse(xrange(n)):
print seqn[i]
::
for elem in seqn.iterreverse():
for elem in inreverse(seqn):
print elem
The new protocol would be applied to lists, tuples, strings, and
xrange objects. It would not apply to unordered collections like
dicts and sets.
The core idea is that the clearest, least error-prone way of specifying
reverse iteration is to specify it in a forward direction and then say
*inreverse*.
No language syntax changes are needed.
The implementation could be as simple as::
def inreverse(x):
i = len(x)
while i > 0:
i -= 1
yield x[i]
If *x* is a mapping, the implementation should return a ValueError with
a message noting that reverse iteration is undefined for mappings.
No language syntax changes are needed. The proposal is fully backwards
compatible.
Alternative Method Names
========================
* *iterbackwards* -- like iteritems() but somewhat long
* *backwards* -- more pithy, less explicit
* *ireverse* -- reminiscent of imap(), izip(), and ifilter()
Other Issues
============
Custom Reverse
==============
* Should *tuple* objects be included? In the past, they have been
denied some list like behaviors such as count() and index(). I
prefer that it be included.
Objects may optionally provide an *__inreverse__* method that returns
a custom reverse iterator.
* Should *file* objects be included? Implementing reverse iteration
may not be easy though it would be useful on occasion. I think
this one should be skipped.
This allows reverse() to be applied to objects that do not have
__getitem__() and __len__() but still have some useful way of
providing reverse iteration.
* Should *enumerate* objects be included? They can provide reverse
iteration only when the underlying sequences support *__len__*
and reverse iteration. I think this can be saved for another
day if the need arises.
Using this protocol, enumerate() can be extended to support reversal
whenever the underlying iterator supports it also.
Real World Use Cases
@ -101,39 +112,8 @@ library and comments on why reverse iteration was necessary:
func, targs, kargs = _exithandlers.pop()
. . .
The application dictates the need to run exit handlers in the
reverse order they were built. The ``while alist: alist.pop()``
form is readable and clean; however, it would be slightly faster
and clearer with::
for func, target, kargs in _exithandlers.iterreverse():
. . .
del _exithandlers
Note, if the order of deletion is important, then the first form
is still needed.
* difflib.get_close_matches() uses::
result.sort() # Retain only the best n.
result = result[-n:] # Move best-scorer to head of list.
result.reverse() # Strip scores.
return [x for score, x in result]
The need for reverse iteration arises from a requirement to return
a portion of a sort in an order opposite of the sort criterion. The
list comprehension is incidental (the third step of a Schwartzian
transform). This particular use case can met with extended slicing,
but the code is somewhat unattractive, hard to visually verify,
and difficult for beginners to construct::
result.sort()
return [x for score, x in result[:-n-1:-1]]
The proposed form is much easier to construct and verify::
result.sort()
return [x for score, x in result[-n:].iterreverse()]
In this application popping is required, so the new function would
not help.
* heapq.heapify() uses ``for i in xrange(n//2 - 1, -1, -1)`` because
higher-level orderings are more easily formed from pairs of
@ -160,8 +140,8 @@ library and comments on why reverse iteration was necessary:
elements from an ever diminishing pool. In fact, the algorithm can
be run in a forward direction but is less intuitive and rarely
presented that way in literature. The replacement code
``for i in xrange(1, len(x)).iterreverse()`` is much easier
to mentally verify.
``for i in inreverse(xrange(1, len(x)))`` is much easier
to verify visually.
* rfc822.Message.__delitem__() uses::
@ -173,32 +153,16 @@ library and comments on why reverse iteration was necessary:
underlying list is altered during iteration.
Alternative Ideas
=================
Rejected Alternative
====================
* Add a builtin function, *riter()* which calls a magic method,
*__riter__*. I see this as more overhead for no additional benefit.
* Several variants were submitted that provided fallback behavior
when *__riter__* is not defined:
- fallback to: ``for i in xrange(len(obj)-1,-1,-1): yield obj[i]``
- fallback to: ``for i in itertools.count(): yield obj[-i]``
- fallback to: ``tmp=list(obj); tmp.reverse(); return iter(tmp)``
All of these attempt to save implementing some object methods at the
expense of adding a new builtin function and of creating a new magic
method name.
The approaches using *__getitem__()* are slower than using a custom
method for each object. Also, the *__getitem__()* variants produce
bizarre results when applied to mappings.
All of the variants crash when applied to an infinite iterator.
The last variant can invisibly slip into a low performance mode
(in terms of time and memory) which could be made more visible with
an explicit ``ro=list(obj); ro.reverse()``.
Several variants were submitted that attempted to apply inreverse()
to all iterables by running the iterable to completion, saving the
results, and then returning a reverse iterator over the results.
While satisfying some notions of full generality, running the input
to the end is contrary to the purpose of using iterators
in the first place. Also, a small disaster ensues if the underlying
iterator is infinite.
Copyright