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:
parent
eefc4df37c
commit
26733836c2
|
@ -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
|
||||
|
||||
|
|
132
pep-0322.txt
132
pep-0322.txt
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue