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