Best way to check that you are at the beginning (the end) of an iterable?

Cameron Simpson cs at zip.com.au
Thu Sep 8 00:48:24 CEST 2011


On 07Sep2011 14:35, Laurent <laurent.payot at gmail.com> wrote:
| What is the simplest way to check that you are at the beginning or at
| the end of an iterable? I'm using enumerate with Python 3.2 (see below)
| but I'm wondering if there would be a better way.
| 
| l = ['a', 'b', 'a', 'c']
| 
| for pos, i in enumerate(l):
|     if pos == 0:
|         print("head =", i)
|     else:
|         print(i)
| 
| I know that Python is not exactly a functional language but wouldn't
| something like "ishead()" or "istail()" be useful?

There are a few reasons these do not exist out of the box (quite aside
from how easy it is to do on the occasions you actually want it).
Tackling ishead and istail in turn...

The "ishead()" would need to be a top level function (like "len()")
because if it were an iterator method, every iterator would need to have
it implemented; currently the number of methods needed to roll your own
iterator is just two (iter and next). ishead() could be done as a top
level function, though it would need the storage cost of an additional
state value to every iterator (i.e. a "first" boolean or equivalent). So
you'd be proposing more memory cost and possibly a retrospective code
change for all the existing planetwide code, for a minor convenient. As
you note, enumerate gets you a pos value, and it is easy enough to write
a for loop like this:

  first = True
  for i in iterable_thing:
    if first:
      print "head =", i
    else:
      print i
    first = False

Your istail() is much worse.

A generator would need to do lookahead to answer istail() in the general
case. Consider iterating over the lines in a file, or better still the
lines coming from a pipeline. Or iteraing over packets received on a
network connection. You can't answer "istail()" there until you have
seen the next line/packet (or EOF/connection close). And that may be an
arbitrary amount of time in the future. You're going to stall your whole
program for such a question?

You can do this easily enough for yourself as an itertools-like thing:
write a wrapper generator that answers ishead() and istail() for
arbitrary iterators. Completely untested example code:

  class BoundSensitiveIterator(object):
    def __init__(self, subiter):
      self.sofar = 0
      self.subiter = subiter
      self.pending = ()
    def iter(self):
      return self
    def next(self):
      self.sofar += 1
      if self.pending is None:
        raise StopIteration
      if self.pending:
        nxt = self.pending[0]
        self.pending = ()
        return nxt
      return self.subiter.next()
    def ishead(self):
      # maybe <= 1, depending on what you want it to mean
      return self.sofar == 1
    def istail(self):
      if self.pending is None:
        return True
      if self.pending:
        return False
      try:
        nxt = self.subiter.next()
      except StopIteration:
        self.pending = None
        return True
      else:
        self.pending = (nxt,)
        return False

  I = BoundSensitiveIterator(other_iterable)
  for n in I:
    print n, "ishead =", I.ishead(), "istail =", I.istail()

You can see it adds some performance and storage overhead, and of course
may stall if you every ask istail() of an "on demand" iterable.

About the only time I do this is my personal "the()" convenience
function:

  def the(list, context=None):
    ''' Returns the first element of an iterable, but requires there to be
        exactly one.
    '''
    icontext="expected exactly one value"
    if context is not None:
      icontext=icontext+" for "+context

    first=True
    for elem in list:
      if first:
        it=elem
        first=False
      else:
        raise IndexError, "%s: got more than one element (%s, %s, ...)" \
                          % (icontext, it, elem)

    if first:
      raise IndexError, "%s: got no elements" % icontext
      
    return it

Which I use as a definite article in places where an iterable _should_
yield exactly one result (eg SQL SELECTs that _ought_ to get exactly
one hit). I can see I wrote that a long time ago - it could do with some
style fixes. And a code scan shows it sees little use:-)

Cheers,
-- 
Cameron Simpson <cs at zip.com.au> DoD#743
http://www.cskk.ezoshosting.com/cs/

Electronic cardboard blurs the line between printed objects and the virtual
world.  - overhead by WIRED at the Intelligent Printing conference Oct2006



More information about the Python-list mailing list