在“ for”循环中检测最后一个元素的pythonic方法是什么?


187

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

有什么更好的办法吗?

注意:我不希望使用hack之类的东西reduce;)


那他的第一个呢?也应该压制它吗?
亚当·马坦

您能否告诉我们元素之间正在做什么?
SilentGhost

2
我想得到一个通用案例的答案,但是在一个具体案例中,我需要这样做是在流上写东西,并在它们之间使用分隔符,就像stream.write(','.join(name_list)),但是在for循环中执行此操作而不连接字符串,因为有许多写操作……
e.tadeu


这个答案的前三行确实对我有帮助,遇到了类似的挑战。
卡达姆

Answers:


151

在大多数情况下,使第一个迭代成为特殊情况而不是最后一个案例更容易(且更便宜):

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以及其他确实需要确定iterable的当前值是否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
没错,这种方法似乎比我的方法更好,至少不需要使用枚举和len。
e.tadeu

是的,但是它增加了另一个if,如果将循环分为两个循环,则可以避免。但是,这仅在迭代庞大的数据列表时才有意义。
亚当·马坦

分为两个循环的问题是它违反了DRY或迫使您定义方法。
e.tadeu

我确实试图理解您的最后一个示例(在我的代码中可以完美地工作),但是我不明白它的工作原理(背后的想法)
Olivier Pons 2015年

1
@OlivierPons您需要了解Python的迭代器协议:我得到一个对象的迭代器,并使用检索第一个值next()。然后,我利用迭代器本身是可迭代的,因此可以在for循环中使用它,直到用尽为止,即从第二个值到最后一个值进行迭代。在此期间,我保留从迭代器本地获取的当前值,而保留yield最后一个值。通过这种方式,我知道还有另外一个价值。在for循环之后,我报告了除最后一个值以外的每个值。
费迪南德·拜尔

20

尽管这个问题已经很老了,但我还是通过Google来到这里的,我发现了一种非常简单的方法:列表切片。假设您要在所有列表条目之间添加“&”。

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

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


7
您刚刚重新实现了join函数:`“&” .join([l中的x的str(x)])
Bryan Oakley

字符串连接效率低下。如果len(l)=1000000在此示例中,程序将运行一段时间。append推荐afaik。l=[1,2,3]; l.append(4);
plhn

18

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

您有一个项目,其后是一系列(在项目之间)对。您也可以将其视为(项目之间)对的序列,后跟一个项目。通常,将第一个元素作为特殊条件,而将所有其他元素作为“标准”条件,则更为简单。

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


4
函数调用比if语句慢得多,因此“浪费执行”参数不成立。
Ferdinand Beyer

1
我不确定函数调用和if语句之间的速度差异与什么有关。关键是这种表述没有if语句,它总是错误的(一次除外)
。– S.Lott

1
我将您的陈述解释为“ ...并且不需要浪费很多时间来执行if条件,该条件始终为假(一次除外)”为“ ...并且速度更快,因为它节省了ifs数”。显然,您只是在指“代码清洁度”?
Ferdinand Beyer

ifPython社区是否真的认为定义函数而不是使用语句更干净?
Markus von Broady

17

如果您只是想修改其中的最后一个元素,data_list则可以使用表示法:

L[-1]

但是,您似乎要做的还不止这些。您的方式并没有错。我什至快速浏览了一些Django代码的模板标签,它们基本上完成了您正在做的事情。


1
我没有修改它,而是使用它来做某事
e.tadeu

4
@ e.tadeu甚至是否修改都没有关系。if data != datalist[-1]:在我看来,将if语句更改为:并使其他所有内容保持一致将是对此进行编码的最佳方法。
spacetyper

8
@spacetyper当最后一个值不唯一时,它会中断。
方舟坤

14

如果项目是唯一的:

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

10

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

您可以在输入数据上使用滑动窗口来窥视下一个值,并使用哨兵来检测上一个值。这适用于任何可迭代的项目,因此您无需事先知道长度。成对实现来自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

3

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

(另请参见此问题:循环中的最后一个元素是否值得单独处理

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


但是,应该将最后一个元素与列表中的所有其他元素一样对待。问题是只能元素之间进行操作。
e.tadeu

在那种情况下,第一个是没有前任的唯一一个。将其分开,然后遍历列表的其余常规代码。
xtofl

3

我喜欢@ 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}')

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


2

您的方式没有错,除非您将有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
但请注意,iterable [-1]不适用于所有可迭代对象(例如不具有len的生成器)
e.tadeu

如果您只想访问循环后的最后一项,则只需使用,item而不是使用来重新计算list[-1]。但尽管如此:我不认为这是OP所要求的,是吗?
费迪南德·拜尔

回复:iterable.__iter__() -请不要__直接调用函数。应该是iter(iterable)
PaulMcG

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


2

如果您要查看清单,对我来说,这也可行:

for j in range(0, len(Array)):
    if len(Array) - j > 1:
        notLast()

2

Google将我带到这个老问题,我想我可以为这个问题添加另一种方法。

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


2

对我而言,处理列表结尾处的特殊情况的最简单,最Python的方法是:

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

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


2

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

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

1

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

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

1

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

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

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

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使用以下方法确保不存在:

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

所以我希望开销是无法避免的。


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

这样,您只需评估列表的长度一次。该页面上的许多解决方案似乎都假定长度是预先不可用的,但这不是您的问题的一部分。如果您有长度,请使用它。


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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.