Как питонный способ обнаружить последний элемент в петле цикла for? - PullRequest
153 голосов
/ 27 октября 2009

Я хотел бы знать, как лучше всего (более компактный и "питонический" способ) выполнить специальную обработку для последнего элемента цикла 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;)

Ответы [ 19 ]

126 голосов
/ 27 октября 2009

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

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
18 голосов
/ 27 октября 2009

'Код между' является примером шаблона 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 )

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

14 голосов
/ 27 октября 2009

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

L[-1]

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

11 голосов
/ 17 июня 2015

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

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

Возвращает '1 & 2 & 3'.

9 голосов
/ 27 октября 2009

Это похоже на подход Антса Аасмы, но без использования модуля 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
8 голосов
/ 04 ноября 2016

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

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
4 голосов
/ 27 октября 2009

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

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

(см. Также этот вопрос: делает последний элемент в петле заслуживает отдельной обработки )

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

2 голосов
/ 27 октября 2009

В вашем пути нет ничего плохого, если только у вас не будет 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

В конце концов, ПОЦЕЛУЙ способ сделать что-то для вас, и это будет работать с любой итерацией, в том числе без __len__:

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

Выходы:

1
2
3
3

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

2 голосов
/ 23 июня 2015

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

...