Что pythonic путь состоит в том, чтобы обнаружить последний элемент в 'для' цикла?

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

Вот то, как я в настоящее время делаю это:

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. ;)

164
задан martineau 24 July 2019 в 02:45
поделиться

8 ответов

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

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 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
131
ответ дан 23 November 2019 в 21:14
поделиться

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

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

2
ответ дан 23 November 2019 в 21: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
>>>
0
ответ дан 23 November 2019 в 21:14
поделиться

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

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

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

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

3
ответ дан 23 November 2019 в 21:14
поделиться

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

L[-1]

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

15
ответ дан 23 November 2019 в 21:14
поделиться

Это похоже на подход Ants 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
10
ответ дан 23 November 2019 в 21:14
поделиться

«Код между» является примером шаблона Хвост-Хвост .

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

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

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

18
ответ дан 23 November 2019 в 21:14
поделиться
Другие вопросы по тегам:

Похожие вопросы: