Updated to reflect comments on comp.lang.python.

This commit is contained in:
Raymond Hettinger 2003-09-25 16:18:28 +00:00
parent c884456428
commit 996d620420
1 changed files with 34 additions and 26 deletions

View File

@ -29,16 +29,15 @@ error prone, unnatural, and not especially readable::
One other current approach involves reversing a list before iterating One other current approach involves reversing a list before iterating
over it. That technique wastes computer cycles, memory, and lines of over it. That technique wastes computer cycles, memory, and lines of
code. Also, it only works with lists (strings, for example, do not code::
define a reverse method)::
rseqn = list(seqn) rseqn = list(seqn)
rseqn.reverse() rseqn.reverse()
for value in rseqn: for value in rseqn:
print value print value
Extending slicing minimizes the code overhead but does nothing for Extending slicing is a third approach that minimizes the code overhead
memory efficiency, beauty, or clarity. but does nothing for memory efficiency, beauty, or clarity.
Reverse iteration is much less common than forward iteration, but it Reverse iteration is much less common than forward iteration, but it
does arise regularly in practice. See `Real World Use Cases`_ below. does arise regularly in practice. See `Real World Use Cases`_ below.
@ -109,6 +108,9 @@ library and comments on why reverse iteration was necessary:
. . . . . .
del _exithandlers del _exithandlers
Note, if the order of deletion is important, then the first form
is still needed.
* difflib.get_close_matches() uses:: * difflib.get_close_matches() uses::
result.sort() # Retain only the best n. result.sort() # Retain only the best n.
@ -149,18 +151,15 @@ library and comments on why reverse iteration was necessary:
* platform._dist_try_harder() uses * platform._dist_try_harder() uses
``for n in range(len(verfiles)-1,-1,-1)`` because the loop deletes ``for n in range(len(verfiles)-1,-1,-1)`` because the loop deletes
selected elements from *verfiles* but needs to leave the rest of selected elements from *verfiles* but needs to leave the rest of
the list intact for further iteration. This use case could be the list intact for further iteration.
addressed with *itertools.ifilter()* but would require the
selection predicate to be in a *lambda* expression. The net
result is less clear and readable than the original. A better
reformulation is to replace the first line with the proposed
method.
* random.shuffle() uses ``for i in xrange(len(x)-1, 0, -1)`` because * random.shuffle() uses ``for i in xrange(len(x)-1, 0, -1)`` because
the algorithm is most easily understood as randomly selecting the algorithm is most easily understood as randomly selecting
elements from an ever diminishing pool. In fact, the algorithm can elements from an ever diminishing pool. In fact, the algorithm can
be run in a forward direction but is less intuitive and rarely be run in a forward direction but is less intuitive and rarely
presented that way in literature. presented that way in literature. The replacement code
``for i in xrange(1, len(x)).iter_backwards()`` is much easier
to mentally verify.
* rfc822.Message.__delitem__() uses:: * rfc822.Message.__delitem__() uses::
@ -172,23 +171,32 @@ library and comments on why reverse iteration was necessary:
underlying list is altered during iteration. underlying list is altered during iteration.
Rejected Alternative Ideas Alternative Ideas
========================== =================
* Add a builtin function, *reverse()* which calls a magic method, * Add a builtin function, *riter()* which calls a magic method,
__riter__. I see this as more overhead for no additional benefit. *__riter__*. I see this as more overhead for no additional benefit.
* Add a builtin function, *reverse()* which does the above, and * Several variants were submitted that provided fallback behavior
if *__riter__* is not found, constructs its own using when *__riter__* is not defined:
*__getitem__*, and if *__getitem__* is not found, builds a list
from *__iter__* and returns a reverse iterator over the new list. - fallback to: ``for i in xrange(len(obj)-1,-1,-1): yield obj[i]``
The advantage is that one function takes care of almost everything - fallback to: ``for i in itertools.count(): yield[obj[-i]]``
that is potentially reversible. A disadvantage is that it can - fallback to: ``tmp=list(obj); tmp.reverse(); return iter(tmp)``
invisibility slip in to a low performance mode (in terms of time
and memory) which would be more visible with an explicit All of these attempt to save implementing some object methods at the
``list(obj).reverse()``. Another problem is that *__getitem__* expense of adding a new builtin function and of creating a new magic
is also used in mappings as well as sequences and that could lead method name.
to bizarre results.
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 ``list(obj).reverse()``.
Copyright Copyright