什么是Pythonic的方式来检测python'for'循环中的最后一个元素?
我想知道对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
;)
大多数情况下, 第一次迭代是最简单的(而且更便宜),而不是最后一次:
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!
除此之外,我不认为有一个总体上优越的解决scheme,因为它取决于你正在做的事情。 例如,如果从列表中构build一个string,那么使用str.join()
比使用for
循环“with special case”更好。
使用相同的原则,但更紧凑:
for i, line in enumerate(data_list): if i > 0: between_items() item()
看起来很熟悉,不是吗? 🙂
对于@ofko以及其他真正需要查找不带len()
的iterable的当前值是否是最后一个值的人,您需要向前看:
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
“之间的代码”就是头尾模式的一个例子。
你有一个项目,其后是一系列(之间,项目)对。 您也可以将其视为一系列(项目之间)对,后跟一个项目。 把第一个要素看作是特殊的,把所有其他要素看作是“标准”的情况一般比较简单。
此外,为了避免重复代码,您必须提供一个函数或其他对象来包含您不想重复的代码。 在if语句中embedded一个总是为false的循环是一种愚蠢的行为。
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 )
这是更可靠的,因为它稍微容易certificate,它不会创build一个额外的数据结构(即列表的副本),并不需要大量的if条件,除了一次总是假的浪费执行。
如果你只是想修改data_list
的最后一个元素,那么你可以简单地使用符号:
L[-1]
但是,看起来你不止这些。 你的方式没有什么错。 我甚至快速浏览了一些Django代码的模板标签,他们基本上在做你正在做的事情。
这与antAasma的方法类似,但不使用itertools模块。 它也是一个滞后的迭代器,在迭代器stream中查找单个元素:
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
虽然这个问题很老,我通过谷歌来到这里,我发现一个很简单的方法:列表切片。 假设你想在所有列表条目之间加一个'&'。
s = "" l = [1, 2, 3] for i in l[:-1]: s = s + str(i) + ' & ' s = s + str(l[-1])
这返回“1&2&3”。
您可以在input数据上使用滑动窗口来查看下一个值,并使用标记来检测最后一个值。 这适用于任何迭代,所以你不需要事先知道长度。 成对实现来自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
除了最后一个元素之外,是不是有可能遍历所有的元素,并且在循环之外处理最后一个元素? 毕竟,创build一个循环做类似于你循环的所有元素的东西; 如果一个元素需要特殊的东西,它不应该在循环中。
(另见这个问题: 最后一个元素在一个循环,值得一个单独的待遇 )
编辑:因为问题更多的是关于“之间”,第一个元素是没有前任的特殊元素,或最后一个元素是特殊的,因为它没有后继者。
使用切片,并检查最后一个元素:
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可能会检测到相同的元素,并为它们重用相同的对象。 例如,对于具有相同值和普通整数的string。
你的方式没有什么问题,除非你有100 000个循环,并且要保存10万个“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
如果觉得我会这样做,对我来说似乎很简单。
如果项目是唯一的:
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 - eg print x if len(list) > 0: for x in list[:-1] #code for x in list[-1]: #code
假设input为迭代器,下面是使用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 >>>
如果你正在浏览这个列表,对我来说也是这样:
for j in range(0, len(Array)): if len(Array) - j > 1: notLast()
谷歌给我带来了这个老问题,我想我可以添加一个不同的方法来解决这个问题。
这里的大多数答案都会处理一个for循环控制的正确处理,但是如果data_list是可破坏的,我build议你从列表中popup这些项,直到最终得到一个空的列表:
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) 。 我发现这个解决scheme更优雅,然后处理next()。
我想到的最简单的解决scheme是:
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
所以我期望的开销是不可select的。
对项目进行一次计数,并跟上剩余项目的数量:
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
这样你只评估一次列表的长度。 这个页面上的许多解决scheme似乎都假定这个长度是不可预先提供的,但这不是你问题的一部分。 如果你有这个长度,就用它。
延迟最后一项的特殊处理,直到循环之后。
>>> for i in (1, 2, 3): ... pass ... >>> i 3