什么是pythonic方法来检测python'for'循环中的最后一个元素?

时间:2009-10-27 11:54:34

标签: for-loop python idioms fencepost

我想知道对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;)

之类的黑客攻击

28 个答案:

答案 0 :(得分:124)

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

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 :(得分:18)

'code between'是 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 除了一次以外总是假的条件。

答案 2 :(得分:14)

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

L[-1]

然而,看起来你做的不止于此。你的方式没有什么不妥。我甚至快速浏览了一些Django code的模板标签,它们基本上就是你正在做的事情。

答案 3 :(得分:9)

这类似于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

答案 4 :(得分:9)

虽然这个问题很老,但我是通过谷歌来到这里的,我发现了一种非常简单的方法:列表切片。让我们说你想要一个'&'在所有列表条目之间。

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

这会返回' 1& 2& 3'

答案 5 :(得分:7)

如果项目是唯一的:

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

答案 6 :(得分:4)

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

答案 7 :(得分:3)

是否有可能迭代所有 - 但是最后一个元素,并将最后一个元素视为循环之外?毕竟,创建一个循环来执行类似于循环的所有元素的操作;如果一个元素需要特殊的东西,它就不应该在循环中。

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

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

答案 8 :(得分:2)

我们可以使用 for-else

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

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

输出:

Jakarta
Surabaya
SEMARANG

答案 9 :(得分:2)

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

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

答案 10 :(得分: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年。

答案 11 :(得分:2)

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

while L:
    e = L.pop(0)
    # process element
    if not L:
        print('Last element has been detected.')

答案 12 :(得分:2)

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

这里的大部分答案都会处理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()

如果你不需要对最后一个元素做任何事情,你甚至可以使用而len(element_list)。我发现这个解决方案更优雅然后处理next()。

答案 13 :(得分:2)

使用切片和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可以检测相同的元素并为它们重用相同的对象。例如,对于具有相同值和公共整数的字符串。

答案 14 :(得分:2)

你的方式没有任何问题,除非你有10万个循环,并希望保存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

OUPUTS:

1
2
3
3

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

答案 15 :(得分:1)

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

>>> for i in (1, 2, 3):
...     pass
...
>>> i
3

答案 16 :(得分:1)

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

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

答案 17 :(得分:1)

只需检查数据是否与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

答案 18 :(得分:0)

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

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

因此,我们总是通过延迟处理一次迭代来向前看一个项目。为了在第一次迭代中跳过某些事情,我只是抓住错误。

当然你需要思考一下,以便在你需要的时候提出NameError

同时保持“counstruct”

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

所以我希望开销是不可取的。

答案 19 :(得分: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

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

答案 20 :(得分:0)

如果你要浏览列表,对我来说这也有用:

#addannouncmentdiv {
    button, input, textarea {
        width: 50%;
    }
}

答案 21 :(得分:0)

对我来说,处理列表末尾特殊情况的最简单,最pythonic的方式是:

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

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

答案 22 :(得分:0)

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

>>> l1 = [1,5,2,3,5,1,7,43]                                                 
>>> [i for i in l1 if l1.index(i)+1==len(l1)]                               
[43]

答案 23 :(得分:0)

除了递增计数,您还可以递减计数:

  nrToProcess = len(list)
  for s in list:
    s.doStuff()
    nrToProcess -= 1
    if nrToProcess==0:  # this is the last one
      s.doSpecialStuff()

答案 24 :(得分: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
>>>

答案 25 :(得分: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

答案 26 :(得分:0)

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

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 错

答案 27 :(得分: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__,但这包括列表、元组、字典和集合就好了。