Python's iterator protocol for an iterator 'items' allows combinations of explicit "next(items)" calls with the implicit calls of a "for item in items:" loop. There are at least three situations in which this can be useful. (While the code posted here is not testable, being incomplete or having pseudocode lines, I have tested full examples of each idiom with 3.2.)

1. Process first item of an iterable separately.

A traditional solution is a flag variable that is tested for each item.

first = True
<other setup>
for item in iterable:
  if first:
    <process first>
    first = False
  else:
    <process non-first>

(I have seen code like this posted on this list several times, including today.)

Better, to me, is to remove the first item *before* the loop.

items = iter(iterable)
<set up with next(items)
for item in items:
  <process non-first>

Sometimes <other setup> and <process first> can be combined in <setup with next(items)>. For instance, "result = []" followed by "result.append(process_first(item))" becomes "result = [process_first(item)]".

The statement containing the explicit next(items) call can optionally be wrapped to explicitly handle the case of an empty iterable in whatever manner is desired.

try:
    <set up with next(items)>
except StopIteration:
    raise ValueError("iterable cannot be empty")


For an iterable known to have a small number of items, the first item can be removed, with only a small copying penalty, with a simple assignment.

first, *rest = iterable

The older and harder to write version of this requires the iterable to be a sequence?

first, rest = seq[0], seq[1:]


2. Process the last item of an iterable differently. As far as I know, this cannot be done for a generic iterable with a flag. It requires a look ahead.

items = iter(iterable)
current = next(items)
for item in items:
  <process non-last current>
  current = item
<process last current>

To treat both first and last differently, pull off the first before binding 'current'. Wrap next() calls as desired.


3. Process the items of an iterable in pairs.

items = iter(iterable)
for first in items:
    second = next(items)
    <process first and second>

This time, StopIteration is raised for an odd number of items. Catch and process as desired. One possibility is to raise ValueError("Iterable must have an even number of items").

A useful example of just sometimes pairing is iterating a (unicode) string by code points ('characters') rather than by code units. This requires combining the surrogate pairs used for supplementary characters when the units are 16 bits.

chars = iter(string)
for char in chars:
    if is_first_surrogate(char):
        char += next(chars)
    yield char

--
Terry Jan Reedy

--
http://mail.python.org/mailman/listinfo/python-list

Reply via email to