Как питонный способ обнаружить последний элемент в цикле 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. ;)

Ответ 1

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

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

Ответ 2

"Код между" - это пример шаблона Head-Tail.

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

Кроме того, чтобы избежать повторения кода, вы должны предоставить функцию или другой объект, чтобы содержать код, который вы не хотите повторять. Внедрение оператора 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 условие, которое всегда ложно, за исключением одного раза.

Ответ 3

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

L[-1]

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

Ответ 4

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

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

Это возвращает '1 и 2 и 3'.

Ответ 5

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

def last_iter(it):
    # Ensure it 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

Ответ 6

если элементы уникальны:

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

Ответ 7

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

Ответ 8

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

(см. также этот вопрос: does-the-last-element-in-a-loop-deserve-a-separate-treatment)

EDIT: поскольку вопрос больше связан с "промежутком", либо первый элемент является особенным, поскольку он не имеет предшественника, либо последний элемент является особенным, поскольку он не имеет преемника.

Ответ 9

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

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

Если мне кажется, что я сделаю так, мне кажется просто.

Ответ 10

Используйте срез и 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 может обнаруживать равные элементы и повторно использовать для них одни и те же объекты. Например, для строк одного и того же значения и общих целых чисел.

Ответ 11

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

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

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()

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

Ответ 12

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

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

Ответ 13

Задержка специальной обработки последнего элемента до тех пор, пока цикл не будет завершен.

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

Ответ 14

Предполагая ввод как итератор, здесь используется метод tee и izip из 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.

Демо:

>>> 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
>>>

Ответ 15

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

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

Ответ 16

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

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

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

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

Также сохраняйте `advstruct

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

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

Ответ 17

Подсчитайте элементы один раз и не отставайте от оставшихся элементов:

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

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

Ответ 18

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

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

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

Ответ 19

some_list = ['gfg', 'fdsfsd', 'dasda']

for i in some_list:
    if i == somelist[-1]:
       make_last_shet_here
    else:
       make ordinary stuff

Ответ 20

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

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

Ответ 21

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

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