Что такое подходящие для Python способ определения последнего элемента в Python 'для' петли?


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

18 125

18 ответов:

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

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

    item()

это будет работать для любого iterable, даже для тех, которые не имеют 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

'код между' является примером Голова-Хвост узор.

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

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

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 )

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

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

L[-1]

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

это похоже на подход Муравьев Aasma, но без использования модуля 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

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

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

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

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

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

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

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

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

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

используйте нарезку и 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>

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

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

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

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

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

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

результаты:

1
2
3
3

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

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

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

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

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

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

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

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

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

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

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

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

также держите ' counstruct

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

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

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

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

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

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

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

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