From: Terry Reedy on
A Python newcomer asked this question on python-ideas list.
I am answering here for the benefit of others.

Example: building a string res with commas separating substrings s from
some sequence. Either the first item added must be s versus ', '+s or
the last must be s versus s+', '.

For building strings, of course, the join method solves the problem,
adding n-1 separators between n items.

items = ['first', 'second', 'last']
print(', '.join(items))
#first, second, last

DISCLAIMER: All of the following code chunks produce the same result,
for the purpose of illustration, but they are NOT the way to build a
string result with dependable efficiency.

To treat the first item differently, either peel it off first ...

it = iter(items)
try: # protect against empty it, simplify if know not
res = next(it)
for s in it: res += ', ' + s
except StopIteration:
res = ''
print(res)
# first, second, last

or use a flag.

res = ''
First = True
for s in items:
if First:
res=s
First=False
else:
res += ', ' + s
print(res)
# first, second, last

There is no way, in general, to know whether next(it) will yield another
item after the current one. That suggests that the way to know whether
an item is the last or not is try to get another first, before
processing the current item.

One approach is to look ahead ...

it = iter(items)
res = ''
try:
cur = next(it)
for nxt in it:
# cur is not last
res += cur + ', '
cur = nxt
else:
# cur is last item
res += cur
except StopIteration:
pass
print(res)
# first, second, last

Another is to add a unique sentinel to the sequence.

Last = object()
items.append(Last) # so not empty, so no protection against that needed
it = iter(items)
res = ''
cur = next(it)
for nxt in it:
if nxt is not Last:
res += cur + ', '
cur = nxt
else:
res += cur

print(res)
# first, second, last

It makes sense to separate last detection from item processing so last
detection can be put in a library module and reused.

def o_last(iterable):
" Yield item,islast pairs"
it = iter(iterable)
cur = next(it)
for nxt in it:
yield cur,False
cur = nxt
else:
yield cur,True

def comma_join(strings):
res = ''
for s,last in o_last(strings):
res += s
if not last: res += ', '
return res

print(comma_join(['first', 'second', 'last']))
print(comma_join(['first', 'last']))
print(comma_join(['last']))
print(comma_join([]))
# first, second, last
# first, last
# last
#

--
Terry Jan Reedy

From: Peter Otten on
Terry Reedy wrote:

> It makes sense to separate last detection from item processing so last
> detection can be put in a library module and reused.

Here's an extension of your idea that puts the detection of both the first
and the last item into a generator:

def mark_first(items):
items = iter(items)
yield True, next(items)
for item in items:
yield False, item

def mark_last(items):
items = iter(items)
prev = next(items)
for cur in items:
yield False, prev
prev = cur
yield True, prev

def mark_ends(items):
return ((head, tail, item) for head, (tail, item)
in mark_first(mark_last(items)))

for items in "", "a", "ab", "abc":
print list(items), "-->", list(mark_ends(items))

for first, last, item in mark_ends("abc"):
if first:
print "first",
if last:
print "last",
print item

It may not be the most efficient approach, but it looks clean.