当使用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

我如何简化或改进它?


当前回答

这是一个老问题,已经有很多很好的回答了,但我觉得这很python:

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

我认为现有枚举的通用性和熟悉性使其最具python性。

注意,与enumerate()不同,rev_enumerate()要求输入实现__len__,但这包括列表、元组、字典和集。

其他回答

我将提供一种更优雅和健壮的方式,如下所示,使用unpacking:

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

我只是遇到了这个问题,我的通用解决方案使用迭代器:

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

大多数情况下,让第一次迭代成为特殊情况比最后一次迭代更容易(也更便宜):

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 i in (1, 2, 3):
...     pass
...
>>> i
3

我喜欢@ethan-t的方法,但从我的角度来看,True是危险的。

data_list = [1, 2, 3, 2, 1]  # sample data
L = list(data_list)  # destroy L instead of data_list
while L:
    e = L.pop(0)
    if L:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')
del L

这里,data_list的值是,最后一个元素的值等于列表的第一个元素。L可以与data_list交换,但在这种情况下,循环后它的结果为空。如果你在处理前检查该列表不为空或检查不需要(哎呀!),也可以使用True。

data_list = [1, 2, 3, 2, 1]
if data_list:
    while True:
        e = data_list.pop(0)
        if data_list:
            print(f'process element {e}')
        else:
            print(f'process last element {e}')
            break
else:
    print('list is empty')

好处是它很快。坏的-它是可销毁的(data_list变成空)。

最直观的解决方案:

data_list = [1, 2, 3, 2, 1]  # sample data
for i, e in enumerate(data_list):
    if i != len(data_list) - 1:
        print(f'process element {e}')
    else:
        print(f'process last element {e}')

哦,是的,你已经提出来了!