在'for'循环中,检测最后一个元素的pythonic方式是什么?

315

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

如何简化或改善这段代码?


第一个呢?它也应该被压制吗? - Adam Matan
你能告诉我们在元素之间正在做什么吗? - SilentGhost
3
我希望你能够提供一般情况下的答案,但实际上我需要解决以下问题:在流中写入内容时,在它们之间加上分隔符,就像 stream.write(', '.join(name_list)) 一样。但是,考虑到要进行多次写入操作,不能使用字符串连接方式来实现。 - e.tadeu
相关链接:https://dev59.com/2XRC5IYBdhLWcg3wW_pk#325864 - codeape
这个答案的前三行真的帮了我,我遇到了类似的挑战。 - cardamom
34个回答

230

大多数情况下,将第一次迭代设为特殊情况而不是最后一次更容易(也更便宜):

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

1
是的,这种方法似乎比我的更好,至少不需要使用enumerate和len。 - e.tadeu
1
@OlivierPons 你需要了解Python的迭代器协议:我获取一个对象的迭代器,并使用next()检索第一个值。然后我利用迭代器本身是可迭代的事实,在for循环中使用它直到耗尽,从第二个到最后一个值进行迭代。在此期间,我将从迭代器中检索到的当前值保留在本地,并yield最后一个值。这样我就知道还有一个值要出现。在for循环之后,我报告了除最后一个值以外的每个值。 - Ferdinand Beyer
21
这并没有回答这个问题。 - Marcos Pereira
@KarlKnechtel 可能是因为大部分答案都是关于处理“第一个”值的。 - undefined
嗯,是的,但具体的重点在于,在那些需要“检测最后一个元素”的情况下,通常可以将问题转化为需要“检测第一个元素”来解决问题,而这样做更容易。 - undefined
显示剩余7条评论

48

如果物品是唯一的:

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]:
        #process everything except the last element
    for x in list[-1:]:
        #process only last element

47

虽然这个问题已经很老了,但我通过谷歌搜索到了这里,并且找到了一个相当简单的方法:列表分片。假设你想在所有列表条目之间放置"&"。

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

这将返回 '1&2&3'。


24
你刚刚重新实现了join函数: " & ".join([str(x) for x in l]) - Bryan Oakley
字符串拼接有些低效。如果在这个例子中len(l)=1000000,程序将运行一段时间。据我所知,建议使用appendl=[1,2,3]; l.append(4); - plhn
7
并非所有可以用for循环迭代的东西都可以使用切片。 - martineau

27

'code between'是Head-Tail模式的一个例子。

你有一个项目,其后跟着一系列 (between, item) 对。你也可以将其视为一系列(item, between)对,后跟一个item。通常比较简单的做法是将第一个元素作为特殊情况,而将其他所有元素均视为“标准”情况。

此外,为了避免重复代码,您必须提供一个函数或其他对象来包含您不想重复的代码。将if语句嵌入一个始终为假的循环中除了一次以外都是有点愚蠢的。

def item_processing( item ):
    # *the common processing*

head_tail_iter = iter( someSequence )
head = next(head_tail_iter)
item_processing( head )
for item in head_tail_iter:
    # *the between processing*
    item_processing( item )

这种方法更可靠,因为它稍微容易证明,它不会创建一个额外的数据结构(即列表的副本),也不需要执行很多无用的if条件,该条件只有一次是真的。


6
函数调用比“if”语句慢得多,因此“浪费执行时间”的论点并不成立。 - Ferdinand Beyer
1
我不确定函数调用和if语句之间的速度差异与任何事情有关。重点是,这个公式没有始终为假的if语句(除了一次)。 - S.Lott
2
我理解你的陈述“...并且不需要执行很多次无用的if条件语句,这些语句除了一次以外都是false”的意思是“...并且更快,因为它节省了几个if语句”。显然,你只是在谈论“代码的清晰度”? - Ferdinand Beyer
1
Python社区真的认为定义一个函数而不是使用“if”语句更加简洁吗? - Markus von Broady

25

如果你只是想修改 data_list 中的最后一个元素,那么你可以使用以下符号:

L[-1]

不过你似乎做得比那更多。你这么做没有什么问题。我甚至快速浏览了一些Django模板标签的代码,他们基本上也是这样做的。


1
我不是修改它,我是用它来做某事。 - e.tadeu
4
即使你没有修改它,也没有关系。将你的 if 语句更改为:if data != datalist[-1]: 并保持其他所有内容不变,我认为这是编写此代码的最佳方式。 - spacetyper
11
当最后一个值不唯一时,这个会出错。 - Ark-kun
这并没有回答问题;它是问题陈述的一部分,要求必须迭代输入并以某种方式处理每个元素 - 只是对于最后一个元素,处理方式不同 - Karl Knechtel

19

您可以使用以下代码确定最后一个元素:

for i,element in enumerate(list):
    if (i==len(list)-1):
        print("last element is" + element)

1
如此简单的解决方案! - Nikola Lukic
9
我们应该在循环之前计算长度,这样它就不会在每次循环中被重新计算。 - daigorocub
1
你可能想把list变量改成items或其他名称。list是一个内置函数 - tony
这个问题涉及到可迭代对象,处理具有长度的对象的最后一个元素是微不足道的。 - undefined

16

这与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

这在功能上与 Ferdinand Beyer 的答案中的 lookahead 迭代器是相同的。 - Karl Knechtel

14

我们可以使用for-else来实现这一点。

cities = [
  'Jakarta',
  'Surabaya',
  'Semarang'
]

for city in cities[:-1]:
  print(city)
else:
  print(' '.join(cities[-1].upper()))

输出:

Jakarta
Surabaya
S E M A R A N G

这个想法是我们只使用for-else循环直到n-1索引,然后在for用尽后,我们可以直接使用[-1]访问最后一个索引。


不错的方法,但对于查询集不起作用:“不支持负索引”。 - cwhisperer
4
这里的else语句是无用的。您可以将print(' '.join(cities[-1].upper()))放在第三行(未缩进),并删除else:行。###只有当for循环中有break时,for-else循环才有用。然后,如果没有触发breakelse语句将执行。参考自https://dev59.com/emkw5IYBdhLWcg3wUI7x - wisbucky
除了无意义地使用 else: 之外,这与 BeckmaR 的答案在功能上是等效的,并没有增加任何额外的见解。 - Karl Knechtel

7
您可以在输入数据上使用滑动窗口来查看下一个值,并使用哨兵来检测最后一个值。这适用于任何可迭代对象,因此您不需要事先知道长度。pairwise 实现来自 itertools recipes
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

5
我将提供一种更优雅和强大的方法,如下所示,使用解包:
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 假


这个实现与 Ferdinand Beyer 的回答中的 lookahead 相同,但我认为它是一个更适用于现代 Python 版本的更干净的实现。在我的测试中,它的性能略低,但不太可能有影响。 - Karl Knechtel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接