254

我想知道对 for 循环中的最后一个元素进行特殊处理的最佳方式(更紧凑和“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.;)

4

31 回答 31

185

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

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
于 2009-10-27T12:02:28.947 回答
35

虽然这个问题很老了,但我通过谷歌来到这里,我发现了一个非常简单的方法:列表切片。假设您想在所有列表条目之间放置一个“&”。

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

这将返回“1 & 2 & 3”。

于 2015-06-17T07:46:09.750 回答
27

如果项目是唯一的:

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
于 2016-11-04T03:56:14.850 回答
21

“代码之间”是头尾模式的一个示例。

您有一个项目,其后是一系列 ( between, item ) 对。您也可以将其视为一系列 (item, between) 对,后跟一个项目。将第一个元素作为特殊元素并将所有其他元素作为“标准”案例通常更简单。

此外,为了避免重复代码,您必须提供一个函数或其他对象来包含您不想重复的代码。将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条件,该条件除了一次之外始终为假。

于 2009-10-27T13:38:36.737 回答
18

如果您只是想修改最后一个元素,data_list那么您可以简单地使用符号:

L[-1]

然而,看起来你做的不止这些。你的方式并没有什么问题。我什至快速浏览了一些Django 代码的模板标签,它们基本上完成了你正在做的事情。

于 2009-10-27T12:05:19.563 回答
13

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

for i,element in enumerate(list):
    if (i==len(list)-1):
        print("last element is" + element)
于 2020-08-18T07:21:14.770 回答
12

这类似于 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
于 2009-10-27T20:32:51.687 回答
8

我们可以使用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]

于 2021-04-20T13:26:34.037 回答
5

您可以在输入数据上使用滑动窗口来查看下一个值,并使用哨兵检测最后一个值。这适用于任何可迭代对象,因此您无需事先知道长度。成对实现来自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
于 2009-10-27T13:17:21.813 回答
4

除了最后一个元素之外,是否没有可能遍历所有元素,并在循环之外处理最后一个元素?毕竟,创建了一个循环来执行类似于您循环的所有元素的操作;如果一个元素需要一些特殊的东西,它不应该在循环中。

(另见这个问题:does-the-last-element-in-a-loop-deserve-a-separate-treatment

编辑:由于问题更多的是关于“介于两者之间”,所以第一个元素是特殊的,因为它没有前任,或者最后一个元素是特殊的,因为它没有后继。

于 2009-10-27T12:11:17.207 回答
4

我喜欢@ethan-t 的方法,但从while 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但在这种情况下,它在循环后结果为空。while 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}')

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

于 2018-03-26T07:48:25.447 回答
3

您的方式没有任何问题,除非您将有 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

但实际上,在你的情况下,我觉得这有点矫枉过正。

在任何情况下,你可能会更幸运 slicing :

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

最终,一个接吻的方式来做你的事情,这将适用于任何可迭代的,包括那些没有__len__

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

输出:

1
2
3
3

如果觉得我会那样做,对我来说似乎很简单。

于 2009-10-27T12:15:19.570 回答
3

使用切片并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>

注意事项:这仅在列表中的所有元素实际上不同(在内存中具有不同位置)时才有效。在底层,Python 可能会检测到相同的元素并为它们重用相同的对象。例如,对于具有相同值和常见整数的字符串。

于 2015-06-23T03:27:22.160 回答
3

谷歌把我带到了这个老问题,我想我可以为这个问题添加不同的方法。

这里的大多数答案都将处理被要求的 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() 更优雅。

于 2016-07-14T14:09:10.217 回答
2

如果您正在浏览列表,对我来说这也很有效:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()
于 2016-01-08T21:51:37.140 回答
2

对我来说,在列表末尾处理特殊情况的最简单和 Pythonic 的方法是:

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

当然,这也可以用于以特殊方式处理第一个元素。

于 2018-07-19T11:55:48.310 回答
2

除了计数,您还可以倒数:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()
于 2019-07-19T17:37:29.767 回答
2

迟到总比不到好。您使用的原始代码enumerate(),但您只使用i索引来检查它是否是列表中的最后一项。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 年后。

于 2020-08-03T19:26:03.143 回答
1

将最后一项的特殊处理延迟到循环之后。

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3
于 2017-08-01T22:54:36.693 回答
1

可以有多种方式。切片将是最快的。再添加一个使用 .index() 方法:

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]
于 2019-02-01T06:46:32.723 回答
1

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

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 假

于 2021-02-22T06:04:22.357 回答
1

如果您乐于破坏列表,那么下面是。

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)


这适用于空列表和非唯一项目列表。由于列表通常是暂时的,因此效果很好……以破坏列表为代价。

于 2021-12-22T12:21:31.293 回答
0

假设输入为迭代器,这是使用 itertools 中的 tee 和 izip 的方法:

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
>>>
于 2009-10-27T13:39:40.647 回答
0

我想到的最简单的解决方案是:

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

所以我们总是通过延迟处理一次迭代来提前一项。要在第一次迭代期间跳过做某事,我只是捕捉到错误。

当然,您需要考虑一下,以便NameError在您需要时提出。

还要保留`construct

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

所以我希望开销是无法选择的。

于 2017-04-03T13:45:23.053 回答
0

计数一次并跟上剩余的项目数:

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

这样,您只需评估一次列表的长度。此页面上的许多解决方案似乎都假设无法提前获得长度,但这不是您问题的一部分。如果你有长度,使用它。

于 2017-05-23T20:04:20.173 回答
0

想到的一个简单的解决方案是:

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
于 2020-01-16T08:30:49.230 回答
0

只需检查数据是否与 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
于 2020-09-24T02:47:28.347 回答
0

所以,这绝对不是“更短”的版本——如果“最短”和“Pythonic”实际上是兼容的,人们可能会离题。

但是如果经常需要这种模式,只需将逻辑放入 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
于 2020-10-15T15:53:52.833 回答
0

这是一个老问题,已经有很多很好的回应,但我觉得这很 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使它最Pythonic。

警告,不像enumerate()rev_enumerate()要求输入实现__len__,但这包括列表、元组、字典和集合就好了。

于 2021-03-17T19:18:47.200 回答
0

如果您正在循环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
于 2021-12-15T06:54:45.250 回答
0

我发现在循环表达式之前定义循环值很方便。对于此框示例,匹配循环中的值,或者您可能需要的任何其他位置。

numberofboxes = 1411

for j in range(1,numberofboxes):
    if j != numberofboxes - 1:
        print ("},")
    else:
        print("}")
于 2022-01-09T21:30:30.867 回答