Как питонный способ обнаружить последний элемент в петле цикла for?

Я хотел бы знать лучший способ (более компактный и "питонический" способ) сделать специальную обработку для последнего элемента в цикле for. Существует фрагмент кода, который следует вызывать только между элементами, подавляя в последнем.

Вот как я сейчас это делаю:

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

Есть ли лучший способ?

Примечание: я не хочу делать это с помощью хаков, таких как использование reduce;)

34 ответа

Решение

В большинстве случаев проще (и дешевле) сделать первую итерацию особой, а не последнюю:

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

    item()

Это будет работать для любого повторяемого, даже для тех, которые не имеют len():

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

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

Кроме того, я не думаю, что есть вообще превосходящее решение, поскольку оно зависит от того, что вы пытаетесь сделать. Например, если вы строите строку из списка, естественно, лучше использовать str.join() чем с помощью for петля "с особым случаем".


Используя тот же принцип, но более компактный:

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

Выглядит знакомо, не правда ли?:)


Для @ofko и других, которым действительно необходимо выяснить, является ли текущее значение повторяемым без len() последний, вам нужно смотреть в будущее:

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

Тогда вы можете использовать это так:

>>> for i, has_more in lookahead(range(3)):
...     print(i, has_more)
0 True
1 True
2 False

Если предметы уникальны:

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

другие опции:

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]
        #code
    for x in list[-1]:
        #code

Хотя этот вопрос довольно старый, я пришел сюда через Google и нашел довольно простой способ: нарезка списка. Допустим, вы хотите поставить '&' между всеми записями списка.

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

Это возвращает "1 & 2 & 3".

"Код между" является примером паттерна Head-Tail.

У вас есть предмет, за которым следует последовательность (между, предметом) пар. Вы также можете просмотреть это как последовательность пар (элемент, между), за которыми следует элемент. Обычно проще воспринимать первый элемент как особый, а все остальные - как "стандартный" случай.

Кроме того, чтобы избежать повторения кода, вы должны предоставить функцию или другой объект, содержащий код, который вы не хотите повторять. Встраивание оператора if в цикл, который всегда ложен, за исключением одного раза, довольно глупо.

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

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

Это более надежно, потому что его немного легче доказать. Он не создает дополнительную структуру данных (то есть копию списка) и не требует много потерянного выполнения условия if, которое всегда ложно, за исключением одного раза.

Если вы просто хотите изменить последний элемент в data_list тогда вы можете просто использовать обозначение:

L[-1]

Тем не менее, похоже, что вы делаете больше, чем это. В твоем пути нет ничего плохого. Я даже бросил быстрый взгляд на некоторый код Django для их шаблонных тегов, и они в основном делают то, что вы делаете.

Вы можете определить последний элемент с помощью этого кода:

for i,element in enumerate(list):
    if (i==len(list)-1):
        print("last element is" + element)

Это похоже на подход Антса Аасмы, но без использования модуля itertools. Это также запаздывающий итератор, который просматривает один элемент в потоке итератора:

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

Мы можем добиться этого, используя for-else

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

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

выход:

      Jakarta
Surabaya
SEMARANG

Вы можете использовать скользящее окно поверх входных данных, чтобы просмотреть следующее значение и использовать часовой для определения последнего значения. Это работает на любой итерации, поэтому вам не нужно заранее знать длину. Парная реализация взята из рецептов itertools.

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

Я предложу более элегантный и надежный способ, используя распаковку:

      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

Тест:

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

Результат:

1 Верно
2 Верно
3 Неверно

Особый случай: входные данные имеют известное «дозорное» значение в конце или известно, что значения различны.

В этом случае мы можем просто проверить значение последнего ввода.

Особый случай: сделайте что-то дополнительное только с последним элементом

Цикл Python не создает отдельную область видимости , что предусмотрено замыслом . Поэтому просто напишите «лишний» код после цикла:

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

Особый случай: сделайте что-то дополнительное «между» каждым элементом

Часто люди понимают эту проблему так: на каждой итерации делать что-то обычное, а затем что-то дополнительное, за исключением пропуска лишней части на последней итерации. Как описано в ответе С.Лотта , это делает проблему слишком сложной. Обнаружить первую итерациюцикл — например, с использованием простой переменной-флага — что также позволяет решить проблему: делать что-то дополнительное, а затем что-то обычное на каждой итерации, за исключением пропуска лишней части на первой итерации. Таким образом:

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

(Конечно, бывают случаи еще более особенные. Например, если «сделать что-нибудь обычное» означает «добавить элемент в выходную строку», а «сделать что-то дополнительное» означает «добавить строку-разделитель в выходную строку», то на самом деле мы соединяем элементы с помощью разделителей между ними .)

Особый случай: обработка перекрывающихся пар элементов.

Иногда люди, которым нужно перебрать перекрывающиеся пары элементов во входных данных, понимают это так: цикл по каждому элементу, кроме последнего; для каждого из этих элементов сделайте что-нибудь с этим элементом и последующим за ним.

На самом деле это очень неуклюжий способ описания проблемы, потому что получить «следующий элемент» от элемента не получится. Нам нужен либо итератор для входных данных, либо значения индекса (для последовательности).

Эту проблему лучше рассматривать как совершенно другую проблему, которая подробно рассматривается здесь: .

Общий случай: сделайте что-то особенное с последним элементом

Если проблема не соответствует ни одному из вышеперечисленных особых случаев, необходимо будет применить более серьезный подход. Вот несколько вариантов:

Проверьте последний элемент по его индексу

Если входные данные представляют собой последовательность (т. е. имеют известную длину), то определить индекс последнего элемента тривиально. Получив доступ к индексу с помощью, то решение проблемы становится простым. Например:

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

Нарезать входные данные

Опять же, если входные данные представляют собой последовательность, мы можем взять срез , содержащий все элементы, кроме последнего, выполнить итерацию по нему, а затем обработать последний элемент:

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

Предполагается, что существует хотя бы один элемент (в противном случаеподнимет). Это можно обработать обычными способами ( путем предварительной явной проверки или стандартной обработкой исключений ); или мы можем перебрать два отдельных фрагмента, чтобы избежать ошибки:

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

Это работает, потому что нарезка не учитывает «отсутствующие» элементы; еслипусто, тобудет просто пустой список.

Невозможно разрезать итерации без определенной длины, поскольку их можно обрабатывать, только просматривая элементы по одному, а простой просмотр элемента не определяет, является ли он последним.не может это исправить (и поэтому явно запрещает отрицательные значения для точки остановки среза).

Используйте предварительный просмотр

См.реализация итератора в ответе Фердинанда Бейера ; это позволяет нам писать такой код:

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

Другая реализация этой идеи — ответ Антса Аасмы . По сути, это переформулирует проблему как проблему Как я могу перебирать перекрывающиеся (текущая, следующая) пары значений из списка?«перебора перекрывающихся пар» , за исключением того, что добавляется контрольное значение, так что последнее значение во входных данных имеет «пару», и затем цикл может просто проверить наличие этого контрольного значения.

Нет ли возможности перебрать все элементы, кроме последнего, и обработать последний вне цикла? В конце концов, создается цикл, который делает что-то похожее на все элементы, над которыми вы зацикливаетесь; если одному элементу нужно что-то особенное, оно не должно быть в цикле.

(см. также этот вопрос: " делает ли последний элемент в цикле достойный отдельного обращения")

РЕДАКТИРОВАТЬ: так как вопрос больше о "между", либо первый элемент является особенным в том, что он не имеет предшественника, или последний элемент является особенным в том, что он не имеет преемника.

Мне нравится подход @ethan-t, но while True опасно с моей точки зрения.

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')

В вашем пути нет ничего плохого, если только у вас не будет 100 000 циклов и вы хотите сохранить 100 000 операторов if. В этом случае вы можете пойти по этому пути:

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

Выходы:

1
2
3
3

Но на самом деле, в вашем случае я чувствую, что это излишне.

В любом случае вам наверняка повезет с нарезкой:

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

#outputs
1
2
last : 3

или просто:

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

#outputs
1
2
3
last : 3

В конце концов, KISS способ сделать что-то, и это будет работать с любой итерацией, в том числе без __len__:

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

Выходы:

1
2
3
3

Если мне кажется, что я бы так поступил, мне кажется, это просто.

Лучше поздно, чем никогда. Ваш исходный код использованenumerate(), но вы использовали только iindex, чтобы проверить, является ли это последним элементом в списке. Вот более простая альтернатива (если вам не нужноenumerate()) с использованием отрицательной индексации:

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] проверяет, является ли текущий элемент в итерации НЕ последним элементом в списке.

Надеюсь, это поможет, даже почти 11 лет спустя.

Для меня самый простой и питонный способ обработки особого случая в конце списка:

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

Конечно, это также может быть использовано для особой обработки первого элемента.

Google привел меня к этому старому вопросу, и я думаю, что мог бы добавить другой подход к этой проблеме.

Большинство ответов здесь будут касаться правильной обработки элемента управления цикла for, как его просили, но если data_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()

Вы можете даже использовать while len (element_list), если вам не нужно ничего делать с последним элементом. Я нахожу это решение более элегантным, чем работа с next().

Используйте нарезку и is проверить последний элемент:

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: Это работает только в том случае, если все элементы в списке на самом деле разные (имеют разные места в памяти). Под капотом Python может обнаружить одинаковые элементы и использовать для них одни и те же объекты. Например, для строк с одинаковым значением и общими целыми числами.

Если вы зацикливаете List, С использованием enumerateфункция является одной из лучших попыток.

      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

Вместо того, чтобы считать, вы также можете отсчитывать:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()

Если вы просматриваете список, для меня это тоже сработало:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

На ум приходит одно простое решение:

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

Второе не менее простое решение может быть получено с помощью счетчика:

# 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

Я только что столкнулся с этим вопросом, и в моем общем решении используется итератор:

      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

вы получите кортеж с исходными элементами и флагами для первого и последнего элемента. Его можно использовать с каждым итерируемым объектом:

      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}')

Это даст вам:

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

Там может быть несколько способов. нарезка будет самой быстрой. Добавляем еще один, который использует метод.index():

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

Если вы счастливы быть разрушительными со списком, то есть следующее.

      while data_list:
   value = data_list.pop(0)
   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)


Это хорошо работает с пустыми списками и списками неуникальных элементов. Поскольку списки часто бывают временными, это работает очень хорошо ... за счет уничтожения списка.

Просто проверьте, не совпадают ли данные с последними данными в 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

Задержка специальной обработки последнего элемента до окончания цикла.

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

Это старый вопрос, и на него уже есть много отличных ответов, но мне показалось, что это довольно 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

Используется так:

      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}')

Или, возможно, вы хотите сделать наоборот:

      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}')

Или просто чтобы узнать, сколько еще осталось ...

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

Думаю универсальность и знакомство с существующими enumerate делает его наиболее питоническим.

Предостережение, в отличие от enumerate(), rev_enumerate() требует, чтобы входной механизм __len__, но это прекрасно включает списки, кортежи, словари и наборы.

Итак, это определенно не "более короткая" версия - и можно отвлечься, если "кратчайшая" и "питоническая" действительно совместимы.

Но если кто-то часто нуждается в этом шаблоне, просто вставьте логику в генератор из 10 строк - и получите любые метаданные, связанные с положением элемента, прямо на forвызов. Еще одно преимущество здесь в том, что он будет работать с произвольной итерацией, а не только с последовательностями.

_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

Самое простое решение, которое приходит мне в голову:

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

Поэтому мы всегда смотрим в будущее на один пункт, задерживая выполнение одной итерации. Чтобы пропустить что-то во время первой итерации, я просто улавливаю ошибку.

Конечно, нужно немного подумать, чтобы NameError быть поднятым, когда вы этого хотите.

Также держите "контрструктуру"

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

Это полагает, что имя new ранее не было определено. Если вы параноик, вы можете убедиться, что new не существует, используя:

try:
    del new
except NameError:
    pass

В качестве альтернативы вы также можете использовать оператор if (if notfirst: print(new) else: notfirst = True). Но, насколько я знаю, накладные расходы больше.


Using `timeit` yields:

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

поэтому я ожидаю, что накладные расходы не будут выбраны.

Другие вопросы по тегам