308

How can I treat the last element of the input specially, when iterating with a for loop? In particular, if there is code that should only occur "between" elements (and not "after" the last one), how can I structure the code?

Currently, I write code like so:

for i, data in enumerate(data_list):
    code_that_is_done_for_every_element
    if i != len(data_list) - 1:
        code_that_is_done_between_elements

How can I simplify or improve this?

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
e.tadeu
  • 5,024
  • 2
  • 20
  • 21
  • What about he first one? Should it be suppressed too? – Adam Matan Oct 27 '09 at 12:04
  • could you tell us what is it being done between elements? – SilentGhost Oct 27 '09 at 12:08
  • 3
    I'd like to get the answer for a generic case, but a concrete case where I need this is writing things on a stream, with separators in between them, just like stream.write(', '.join(name_list)), but doing it in a for loop without concatenating the strings, because there are many writes... – e.tadeu Oct 27 '09 at 12:32
  • Related: http://stackoverflow.com/questions/323750/how-to-access-previous-next-element-while-for-looping/325864#325864 – codeape Oct 27 '09 at 14:27
  • The first three lines of [this answer](https://stackoverflow.com/a/2429118/4288043) really helped me, was having a similar challenge. – cardamom Feb 04 '19 at 12:17

34 Answers34

226

Most of the times it is easier (and cheaper) to make the first iteration the special case instead of the last one:

first = True
for data in data_list:
    if first:
        first = False
    else:
        between_items()

    item()

This will work for any iterable, even for those that have no len():

file = open('/path/to/file')
for line in file:
    process_line(line)

    # No way of telling if this is the last line!

Apart from that, I don't think there is a generally superior solution as it depends on what you are trying to do. For example, if you are building a string from a list, it's naturally better to use str.join() than using a for loop “with special case”.


Using the same principle but more compact:

for i, line in enumerate(data_list):
    if i > 0:
        between_items()
    item()

Looks familiar, doesn't it? :)


For @ofko, and others who really need to find out if the current value of an iterable without len() is the last one, you will need to look ahead:

def lookahead(iterable):
    """Pass through all values from the given iterable, augmented by the
    information if there are more values to come after the current one
    (True), or if it is the last value (False).
    """
    # Get an iterator and pull the first value.
    it = iter(iterable)
    last = next(it)
    # Run the iterator to exhaustion (starting from the second value).
    for val in it:
        # Report the *previous* value (more to come).
        yield last, True
        last = val
    # Report the last value.
    yield last, False

Then you can use it like this:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False
Ferdinand Beyer
  • 64,979
  • 15
  • 154
  • 145
  • 1
    True, this way seems better than mine, at least it don't need to use enumerate and len. – e.tadeu Oct 27 '09 at 12:11
  • Yes, but it adds another `if` which could be avoided if the loop was split into two loops. However, this is relevant only when iterating a huge data list. – Adam Matan Oct 27 '09 at 12:13
  • The problem with splitting into two loops is that it either violates DRY or it forces you to define methods. – e.tadeu Oct 27 '09 at 12:15
  • I really try to understand your last example (which works flawlessly in my code), but I dont understand how it works (the idea behind) – Olivier Pons Dec 06 '15 at 16:29
  • 1
    @OlivierPons You need to understand Python's iterator protocol: I get an iterator for an object, and retrieve the first value with `next()`. Then I exploit that an iterator is iterable by itself, so I can use it in the `for` loop until exhaustion, iteration from the second to the last value. During this I keep the current value I retrieved from the iterator locally and `yield` the last one instead. This way I know there is one more value to come. After the for loop, I have reported every value but the last one. – Ferdinand Beyer Dec 06 '15 at 20:50
  • 17
    This does not answer the question – Marcos Pereira Aug 12 '20 at 14:41
  • This answer seems rather confusing. Surely there must be a simpler way. – agftrading Jan 24 '22 at 22:30
  • The answer here from Mattwmaster58 is much more on point: https://stackoverflow.com/questions/39808908/detect-if-item-is-the-last-in-a-list?noredirect=1&lq=1 – agftrading Jan 24 '22 at 22:41
  • Note that in Python 3.7+, you need to wrap `lookahead`'s `next()` call in a try/except StopIteration or you'll get `RuntimeError: generator raised StopIteration`. See https://stackoverflow.com/a/51701040/4243. – kris Nov 24 '22 at 13:51
  • @MarcosPereira I cannot understand why you believe this to be the case. What do you think is being asked, that is different from what is answered here? – Karl Knechtel Mar 29 '23 at 05:55
47

if the items are unique:

for x in list:
    #code
    if x == list[-1]:
        #code

other options:

pos = -1
for x in list:
    pos += 1
    #code
    if pos == len(list) - 1:
        #code


for x in list:
    #code
#code - e.g. print x


if len(list) > 0:
    for x in list[:-1]:
        #process everything except the last element
    for x in list[-1:]:
        #process only last element
lszrh
  • 1,531
  • 1
  • 19
  • 28
Palza
  • 642
  • 5
  • 12
46

Although that question is pretty old, I came here via google and I found a quite simple way: List slicing. Let's say you want to put an '&' between all list entries.

s = ""
l = [1, 2, 3]
for i in l[:-1]:
    s = s + str(i) + ' & '
s = s + str(l[-1])

This returns '1 & 2 & 3'.

BeckmaR
  • 593
  • 4
  • 4
  • 24
    You've just reimplemented the join function: `" & ".join([str(x) for x in l]) – Bryan Oakley Jan 21 '16 at 16:45
  • string concatenation is somewhat inefficient. If `len(l)=1000000` in this example, program will run for a while. `append`is recommended afaik. `l=[1,2,3]; l.append(4);` – plhn Apr 10 '17 at 06:30
  • 7
    Slicing isn't an option for all things that a `for` can iterate. – martineau Oct 18 '20 at 15:07
26

The 'code between' is an example of the Head-Tail pattern.

You have an item, which is followed by a sequence of ( between, item ) pairs. You can also view this as a sequence of (item, between) pairs followed by an item. It's generally simpler to take the first element as special and all the others as the "standard" case.

Further, to avoid repeating code, you have to provide a function or other object to contain the code you don't want to repeat. Embedding an if statement in a loop which is always false except one time is kind of silly.

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = next(head_tail_iter)
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

This is more reliable because it's slightly easier to prove, It doesn't create an extra data structure (i.e., a copy of a list) and doesn't require a lot of wasted execution of an if condition which is always false except once.

jsbueno
  • 99,910
  • 10
  • 151
  • 209
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 6
    Function calls are way slower then `if` statements so the “wasted execution” argument does not hold. – Ferdinand Beyer Nov 04 '09 at 18:41
  • 1
    I'm not sure what the speed difference between function call and if-statement has to do with anything. The point is that this formulation has no if-statement that's always false (except once.) – S.Lott Nov 04 '09 at 18:43
  • 2
    I interpreted your statement “…and doesn't require a lot of wasted execution of an if condition which is always false except once” as “…and is faster since it saves a couple of `if`s”. Obviously you are just refering to “code cleanliness”? – Ferdinand Beyer Nov 04 '09 at 18:52
  • 1
    Is defining a function instead of using an `if` statement really considered cleaner by Python community? – Markus von Broady Sep 25 '19 at 07:11
24

If you're simply looking to modify the last element in data_list then you can simply use the notation:

L[-1]

However, it looks like you're doing more than that. There is nothing really wrong with your way. I even took a quick glance at some Django code for their template tags and they do basically what you're doing.

Bartek
  • 15,269
  • 2
  • 58
  • 65
  • 1
    I'm not modifying it, I'm using it to do something – e.tadeu Oct 27 '09 at 12:20
  • 4
    @e.tadeu it doesn't even matter if you're modifying it or not. Changing your if statement to: `if data != datalist[-1]:` and keeping everything else the same would be the best way to code this in my opinion. – spacetyper Aug 28 '15 at 20:29
  • 11
    @spacetyper This breaks when the last value is non-unique. – Ark-kun Dec 29 '17 at 14:16
  • This doesn't answer the question; it's part of the problem statement that the input must be iterated over and each element processed in some way - it's just that the processing is *different* for the last element. – Karl Knechtel Mar 29 '23 at 07:26
19

you can determine the last element with this code :

for i,element in enumerate(list):
    if (i==len(list)-1):
        print("last element is" + element)
hassanzadeh.sd
  • 3,091
  • 1
  • 17
  • 26
  • 1
    So simple solution! – Nikola Lukic Mar 03 '21 at 19:48
  • 9
    We should calculate the length before the loop. This way it will be calculated every loop. – daigorocub Aug 11 '21 at 16:19
  • 1
    You may want to change the `list` variable to `items` or something else. [`list`](https://docs.python.org/3/library/functions.html#func-list) is a [built-in function](https://docs.python.org/3/library/functions.html). – tony Mar 10 '22 at 15:48
15

This is similar to Ants Aasma's approach but without using the itertools module. It's also a lagging iterator which looks-ahead a single element in the iterator stream:

def last_iter(it):
    # Ensure it's an iterator and get the first field
    it = iter(it)
    prev = next(it)
    for item in it:
        # Lag by one item so I know I'm not at the end
        yield 0, prev
        prev = item
    # Last item
    yield 1, prev

def test(data):
    result = list(last_iter(data))
    if not result:
        return
    if len(result) > 1:
        assert set(x[0] for x in result[:-1]) == set([0]), result
    assert result[-1][0] == 1

test([])
test([1])
test([1, 2])
test(range(5))
test(xrange(4))

for is_last, item in last_iter("Hi!"):
    print is_last, item
Andrew Dalke
  • 14,889
  • 4
  • 39
  • 54
14

We can achieve that using for-else

cities = [
  'Jakarta',
  'Surabaya',
  'Semarang'
]

for city in cities[:-1]:
  print(city)
else:
  print(' '.join(cities[-1].upper()))

output:

Jakarta
Surabaya
S E M A R A N G

The idea is we only using for-else loops until n-1 index, then after the for is exhausted, we access directly the last index using [-1].

Nurul Huda
  • 1,438
  • 14
  • 12
  • Nice approach, but does not work for queryset: "Negative indexing is not supported." – cwhisperer Jun 30 '21 at 06:12
  • 4
    The `else:` statement is useless here. You can just put `print(' '.join(cities[-1].upper()))` as the third line (unindented) and delete the `else:` line. ### The `for-else` loop is only useful when there is a `break` inside the `for` loop. Then `else` will only execute if the `break` was not triggered. https://stackoverflow.com/questions/9979970/why-does-python-use-else-after-for-and-while-loops – wisbucky Oct 01 '21 at 04:32
  • Aside from the pointless use of `else:`, this is functionally equivalent to BeckmaR's answer and adds no additional insight. – Karl Knechtel Mar 29 '23 at 07:24
6

You can use a sliding window over the input data to get a peek at the next value and use a sentinel to detect the last value. This works on any iterable, so you don't need to know the length beforehand. The pairwise implementation is from itertools recipes.

from itertools import tee, izip, chain

def pairwise(seq):
    a,b = tee(seq)
    next(b, None)
    return izip(a,b)

def annotated_last(seq):
    """Returns an iterable of pairs of input item and a boolean that show if
    the current item is the last item in the sequence."""
    MISSING = object()
    for current_item, next_item in pairwise(chain(seq, [MISSING])):
        yield current_item, next_item is MISSING:

for item, is_last_item in annotated_last(data_list):
    if is_last_item:
        # current item is the last item
Ants Aasma
  • 53,288
  • 15
  • 90
  • 97
4

Is there no possibility to iterate over all-but the last element, and treat the last one outside of the loop? After all, a loop is created to do something similar to all elements you loop over; if one element needs something special, it shouldn't be in the loop.

(see also this question: does-the-last-element-in-a-loop-deserve-a-separate-treatment)

EDIT: since the question is more about the "in between", either the first element is the special one in that it has no predecessor, or the last element is special in that it has no successor.

Community
  • 1
  • 1
xtofl
  • 40,723
  • 12
  • 105
  • 192
  • But the last element should be treated similar to every other element in the list. The problem is the thing that should be done only *between* elements. – e.tadeu Oct 27 '09 at 12:16
  • In that case, the first one is the only one without a predecessor. Take that one apart, and loop over the rest of the list general code. – xtofl Oct 27 '09 at 12:39
4

I will provide with a more elegant and robust way as follows, using unpacking:

def mark_last(iterable):
    try:
        *init, last = iterable
    except ValueError:  # if iterable is empty
        return

    for e in init:
        yield e, True
    yield last, False

Test:

for a, b in mark_last([1, 2, 3]):
    print(a, b)

The result is:

1 True
2 True
3 False

manxisuo
  • 41
  • 3
  • This implements the same iterator as `lookahead` in Ferdinand Beyer's answer, but I think it is a much cleaner implementation for modern versions of Python. It is slightly less performant in my testing, but not in a way that is likely to matter. – Karl Knechtel Mar 29 '23 at 07:33
3

Use slicing and is to check for the last element:

for data in data_list:
    <code_that_is_done_for_every_element>
    if not data is data_list[-1]:
        <code_that_is_done_between_elements>

Caveat emptor: This only works if all elements in the list are actually different (have different locations in memory). Under the hood, Python may detect equal elements and reuse the same objects for them. For instance, for strings of the same value and common integers.

Roger Dahl
  • 15,132
  • 8
  • 62
  • 82
3

I like the approach of @ethan-t, but while True is dangerous from my point of view.

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

Here, data_list is so that last element is equal by value to the first one of the list. L can be exchanged with data_list but in this case it results empty after the loop. while True is also possible to use if you check that list is not empty before the processing or the check is not needed (ouch!).

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

The good part is that it is fast. The bad - it is destructible (data_list becomes empty).

Most intuitive solution:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

Oh yes, you have already proposed it!

  • Repeatedly performing `data_list.pop(0)` is slow for large lists, because the entire remaining contents of the list must be shifted down into the previous positions. – Karl Knechtel Mar 29 '23 at 07:12
3

If you are looping the List, Using enumerate function is one of the best try.

for index, element in enumerate(ListObj):
    # print(index, ListObj[index], len(ListObj) )

    if (index != len(ListObj)-1 ):
        # Do things to the element which is not the last one
    else:
        # Do things to the element which is the last one
W Kenny
  • 1,855
  • 22
  • 33
2

There is nothing wrong with your way, unless you will have 100 000 loops and wants save 100 000 "if" statements. In that case, you can go that way :

iterable = [1,2,3] # Your date
iterator = iter(iterable) # get the data iterator

try :   # wrap all in a try / except
    while 1 : 
        item = iterator.next() 
        print item # put the "for loop" code here
except StopIteration, e : # make the process on the last element here
    print item

Outputs :

1
2
3
3

But really, in your case I feel like it's overkill.

In any case, you will probably be luckier with slicing :

for item in iterable[:-1] :
    print item
print "last :", iterable[-1]

#outputs
1
2
last : 3

or just :

for item in iterable :
    print item
print iterable[-1]

#outputs
1
2
3
last : 3

Eventually, a KISS way to do you stuff, and that would work with any iterable, including the ones without __len__ :

item = ''
for item in iterable :
    print item
print item

Ouputs:

1
2
3
3

If feel like I would do it that way, seems simple to me.

Bite code
  • 578,959
  • 113
  • 301
  • 329
  • 2
    But note that iterable[-1] will not work to *all* iterables (such as generator that do not have __len__) – e.tadeu Oct 27 '09 at 12:23
  • If all you want is to access the last item after the loop, simply use `item` instead of re-calculating it using `list[-1]`. But nevertheless: I don't think this is what the OP was asking for, was it? – Ferdinand Beyer Oct 27 '09 at 12:27
  • 1
    Re: `iterable.__iter__() ` - please don't call `__` functions directly. Should be `iter(iterable)`. – PaulMcG Oct 28 '09 at 02:16
  • In 3.x, `iterator.next()` should instead be `next(iterator)`. However, this is really unpythonic. Digging into the details of the iterator protocol shouldn't be necessary for this task - `for` loops exist specifically so that code like this isn't necessary for normal cases. – Karl Knechtel Mar 29 '23 at 07:15
2

Google brought me to this old question and I think I could add a different approach to this problem.

Most of the answers here would deal with a proper treatment of a for loop control as it was asked, but if the data_list is destructible, I would suggest that you pop the items from the list until you end up with an empty list:

while True:
    element = element_list.pop(0)
    do_this_for_all_elements()
    if not element:
        do_this_only_for_last_element()
        break
    do_this_for_all_elements_but_last()

you could even use while len(element_list) if you don't need to do anything with the last element. I find this solution more elegant then dealing with next().

Anderson Santos
  • 169
  • 1
  • 1
  • 6
2

For me the most simple and pythonic way to handle a special case at the end of a list is:

for data in data_list[:-1]:
    handle_element(data)
handle_special_element(data_list[-1])

Of course this can also be used to treat the first element in a special way .

Chris
  • 1,266
  • 4
  • 16
  • 34
2

Instead of counting up, you can also count down:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()
jeroent
  • 2,008
  • 2
  • 15
  • 8
2

Special case: input has a known "sentinel" value at the end, or values are known to be distinct

In this case, we can simply check for the value of the last input.

Special case: do something extra with the last element only

Python's for loop does not create a separate scope, which is by design. Therefore, just write the "extra" code after the loop:

for element in iterable:
    do_normal_thing(element)
do_extra_thing(element)

Special case: do something extra "between" each element

Often, people conceive of this problem as: do something normal and then something extra on each iteration, except skipping the extra part on the last iteration. As described in S.Lott's answer, this makes the problem too complex. It is trivial to detect the first iteration of a for loop - for example, by using a simple flag variable - which also allows for solving the problem: do something extra and then something normal on each iteration, except skipping the extra part on the first iteration. Thus:

first = True
for element in iterable:
    if first:
        do_extra_thing(element)
        first = False
    do_normal_thing(element)

(Of course, there are cases that are even more special. For example, if "do something normal" means "append the element to an output string", and "do something extra" means "append a separator string to the output", then what we are really doing is joining up the elements with separators in between.)

Special case: handle overlapping pairs of elements

Sometimes people who need to loop over overlapping pairs of elements in the input, will conceive of this as: loop over every element except the last; for each of those elements, do something with that element and the one after it.

This is actually a very awkward way to describe the problem, because getting the "next element" from an element does not work. We need either an iterator over the input, or index values (for a sequence).

This problem is better considered as a completely different problem, which is covered in depth here: How can I iterate over overlapping (current, next) pairs of values from a list?.

General case: do something special with the last element

If the problem doesn't match either of the above special cases, it will be necessary to take a more heavy-duty approach. Here are some options:

Verify the last element by its index

If the input is a sequence (i.e., it has a known length), then it is trivial to determine the index of the last element. By accessing the index using enumerate, then, it becomes straightforward to solve the problem. For example:

for index, element in enumerate(sequence):
    if index == len(sequence) - 1:
        do_something_special(element)
    else:
        do_something_normal(element)

Slice the input

Again if the input is a sequence, we can take a slice that contains every element but the last, iterate over that, and then handle the last element:

for element in sequence[:-1]:
    do_something_normal(element)
do_something_special(sequence[-1])

This assumes that there is at least one element (otherwise, element[-1] will raise an IndexError). This can be handled in the usual ways (by an explicit check ahead of time, or with standard exception handling); or we can iterate over two separate slices to avoid the error:

for element in sequence[:-1]:
    do_something_normal(element)
for element in sequence[-1:]:
    do_something_special(element)

This works because slicing doesn't care about "missing" elements; if sequence is empty, then sequence[-1:] will simply be an empty list.

It's not possible to slice iterables without a definite length, because they can only be processed by looking at the elements one at a time, and simply looking at an element doesn't determine whether it's the last one. itertools.islice cannot fix this (and therefore explicitly disallows negative values for the stop point of the slice).

Use lookahead

See the lookahead iterator implementation in Ferdinand Beyer's answer; this allows us to write code like:

for element, is_last in lookahead(sequence):
    if is_last:
        do_something_special(element)
    else:
        do_something_normal(element)

Another implementation of this idea is Ants Aasma's answer. This basically reframes the problem as a "iterate over overlapping pairs" problem, except that a sentinel value is added so that the last value in the input has a "pair", and the loop can then simply check for that sentinel.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
1

if you are going through the list, for me this worked too:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()
tsf144
  • 817
  • 1
  • 8
  • 14
1

Delay the special handling of the last item until after the loop.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3
user240515
  • 3,056
  • 1
  • 27
  • 34
1

There can be multiple ways. slicing will be fastest. Adding one more which uses .index() method:

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
1

If you are happy to be destructive with the list, then there's the following. We are going to reverse the list in order to speed up the process from O(n^2) to O(n), because pop(0) moves the list each iteration - cf. Nicholas Pipitone's comment below

data_list.reverse()
while data_list:
   value = data_list.pop()
   code_that_is_done_for_every_element(value)
   if data_list:
       code_that_is_done_between_elements(value)
   else:
       code_that_is_done_for_last_element(value)


This works well with empty lists, and lists of non-unique items. Since it's often the case that lists are transitory, this works pretty well ... at the cost of destructing the list.

Konchog
  • 1,920
  • 19
  • 23
  • 1
    That's a little rough because it's N^2. The moment you do that with even a list of 10,000 elements, you'll feel the lag. – Nicholas Pipitone Jul 24 '22 at 05:47
  • @NicholasPipitone, how do you get N^2? I get O(n). list.pop() with no arguments removes the last element. Accessing that element can be done in constant time. There are no elements following so nothing needs to be shifted. I answered my own question. – Konchog Jul 25 '22 at 14:33
  • 1
    Yes, it's O(n) now. Re: "how do you get N^2?" That was the list.pop(0) version, the current version is good. – Nicholas Pipitone Jul 25 '22 at 19:13
0

Assuming input as an iterator, here's a way using tee and izip from itertools:

from itertools import tee, izip
items, between = tee(input_iterator, 2)  # Input must be an iterator.
first = items.next()
do_to_every_item(first)  # All "do to every" operations done to first item go here.
for i, b in izip(items, between):
    do_between_items(b)  # All "between" operations go here.
    do_to_every_item(i)  # All "do to every" operations go here.

Demo:

>>> def do_every(x): print "E", x
...
>>> def do_between(x): print "B", x
...
>>> test_input = iter(range(5))
>>>
>>> from itertools import tee, izip
>>>
>>> items, between = tee(test_input, 2)
>>> first = items.next()
>>> do_every(first)
E 0
>>> for i,b in izip(items, between):
...     do_between(b)
...     do_every(i)
...
B 0
E 1
B 1
E 2
B 2
E 3
B 3
E 4
>>>
Anon
  • 11,870
  • 3
  • 23
  • 19
0

The most simple solution coming to my mind is:

for item in data_list:
    try:
        print(new)
    except NameError: pass
    new = item
print('The last item: ' + str(new))

So we always look ahead one item by delaying the the processing one iteration. To skip doing something during the first iteration I simply catch the error.

Of course you need to think a bit, in order for the NameError to be raised when you want it.

Also keep the `counstruct

try:
    new
except NameError: pass
else:
    # continue here if no error was raised

This relies that the name new wasn't previously defined. If you are paranoid you can ensure that new doesn't exist using:

try:
    del new
except NameError:
    pass

Alternatively you can of course also use an if statement (if notfirst: print(new) else: notfirst = True). But as far as I know the overhead is bigger.


Using `timeit` yields:

    ...: try: new = 'test' 
    ...: except NameError: pass
    ...: 
100000000 loops, best of 3: 16.2 ns per loop

so I expect the overhead to be unelectable.

DerWeh
  • 1,721
  • 1
  • 15
  • 26
0

Count the items once and keep up with the number of items remaining:

remaining = len(data_list)
for data in data_list:
    code_that_is_done_for_every_element

    remaining -= 1
    if remaining:
        code_that_is_done_between_elements

This way you only evaluate the length of the list once. Many of the solutions on this page seem to assume the length is unavailable in advance, but that is not part of your question. If you have the length, use it.

Ethan T
  • 1,390
  • 12
  • 28
0

Better late than never. Your original code used enumerate(), but you only used the i index to check if it's the last item in a list. Here's an simpler alternative (if you don't need enumerate()) using negative indexing:

for data in data_list:
    code_that_is_done_for_every_element
    if data != data_list[-1]:
        code_that_is_done_between_elements

if data != data_list[-1] checks if the current item in the iteration is NOT the last item in the list.

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153
howdoicode
  • 779
  • 8
  • 16
0

So, this is definitely not the "shorter" version - and one might digress if "shortest" and "Pythonic" are actually compatible.

But if one needs this pattern often, just put the logic in to a 10-liner generator - and get any meta-data related to an element's position directly on the for call. Another advantage here is that it will work wit an arbitrary iterable, not only Sequences.

_sentinel = object()

def iter_check_last(iterable):
    iterable = iter(iterable)
    current_element = next(iterable, _sentinel)
    while current_element is not _sentinel:
        next_element = next(iterable, _sentinel)
        yield (next_element is _sentinel, current_element)
        current_element = next_element
In [107]: for is_last, el in iter_check_last(range(3)):
     ...:     print(is_last, el)
     ...: 
     ...: 
False 0
False 1
True 2
jsbueno
  • 99,910
  • 10
  • 151
  • 209
0

This is an old question, and there's already lots of great responses, but I felt like this was pretty Pythonic:

def rev_enumerate(lst):
    """
    Similar to enumerate(), but counts DOWN to the last element being the
    zeroth, rather than counting UP from the first element being the zeroth.

    Since the length has to be determined up-front, this is not suitable for
    open-ended iterators.

    Parameters
    ----------
    lst : Iterable
        An iterable with a length (list, tuple, dict, set).

    Yields
    ------
    tuple
        A tuple with the reverse cardinal number of the element, followed by
        the element of the iterable.
    """
    length = len(lst) - 1
    for i, element in enumerate(lst):
        yield length - i, element

Used like this:

for num_remaining, item in rev_enumerate(['a', 'b', 'c']):
    if not num_remaining:
        print(f'This is the last item in the list: {item}')

Or perhaps you'd like to do the opposite:

for num_remaining, item in rev_enumerate(['a', 'b', 'c']):
    if num_remaining:
        print(f'This is NOT the last item in the list: {item}')

Or, just to know how many remain as you go...

for num_remaining, item in rev_enumerate(['a', 'b', 'c']):
    print(f'After {item}, there are {num_remaining} items.')

I think the versatility and familiarity with the existing enumerate makes it most Pythonic.

Caveat, unlike enumerate(), rev_enumerate() requires that the input implement __len__, but this includes lists, tuples, dicts and sets just fine.

mkoistinen
  • 7,724
  • 3
  • 41
  • 56
0

I just came across this question and my generic solution uses an Iterator:

from typing import TypeVar, Iterable
E = TypeVar('E')

def metait(i: Iterable[E]) -> Iterable[tuple[E, bool, bool]]:

    first = True
    previous = None
    for elem in i:
        if previous:
            yield previous, first, False
            first = False
        previous = elem

    if previous:
        yield previous, first, True

you will receive a tuple with the original elements and flags for the first and last item. It can be used with every iterable:

d = {'a': (1,2,3), 'b': (4,5,6), 'c': (7,8,9)}

for (k,v), is_first, is_last in metait(d.items()):
    print(f'{k}: {v}  {is_first} {is_last}')

This will give you:

a: (1, 2, 3)  True False
b: (4, 5, 6)  False False
c: (7, 8, 9)  False True
Florian
  • 46
  • 3
-1

One simple solution that comes to mind would be:

for i in MyList:
    # Check if 'i' is the last element in the list
    if i == MyList[-1]:
        # Do something different for the last
    else:
        # Do something for all other elements

A second equally simple solution could be achieved by using a counter:

# Count the no. of elements in the list
ListLength = len(MyList)
# Initialize a counter
count = 0

for i in MyList:
    # increment counter
    count += 1
    # Check if 'i' is the last element in the list
    # by using the counter
    if count == ListLength:
        # Do something different for the last
    else:
        # Do something for all other elements
AnythingIsFine
  • 1,777
  • 13
  • 11
  • 2
    The first fails for non-unique values. The second is a 'c' like approach that masks using enumerate. please fix for duplicate items, or mark the constraint (only use this for lists of unique values)... – Konchog Dec 22 '21 at 12:13
-1

Just check if data is not the same as the last data in data_list (data_list[-1]).

for data in data_list:
    code_that_is_done_for_every_element
    if data != data_list[- 1]:
        code_that_is_done_between_elements
Risyad
  • 177
  • 1
  • 3
  • 1
    This will fail if there are repeat values in the list. Please fix for duplicate items, or mark the constraint (only use this for lists of unique values) – Konchog Dec 22 '21 at 12:09
-1

I have found it convenient to define the loop value before the loop expression. For this box example, match the value in the loop, or wherever else you may need it.

numberofboxes = 1411

for j in range(1,numberofboxes):
    if j != numberofboxes - 1:
        print ("},")
    else:
        print("}")
Gino Mempin
  • 25,369
  • 29
  • 96
  • 135
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 09 '22 at 22:21
-1

I've shared two simple methods below to find the end of the loop.

Method 1:

num_list = [1, 2, 3, 4]

for n in num_list:
    if num_list[-1] == n:
        print('this is the last iteration of the loop')

Method 2:

num_list = [1, 2, 3, 4]

loop_count = len(num_list) - 1  # 3
for index, num in enumerate(num_list):
    if index == loop_count:
        print('this is the last iteration of the loop')
Praveen Kumar
  • 849
  • 8
  • 8