2009-10-27 53 views
104

我想知道對for循環中最後一個元素進行特殊處理的最佳方法(更緊湊和「pythonic」方式)。有一段代碼只能在元素之間調用,在最後一個元素中被抑制。什麼是Pythonic的方式來檢測python'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 

有沒有什麼更好的辦法?

注:我不想和黑客等利用reduce使它;)

+0

那他第一次呢?它是否也應該被壓制? – 2009-10-27 12:04:29

+0

你能告訴我們元素之間做了什麼嗎? – SilentGhost 2009-10-27 12:08:59

+1

我希望得到一個通用案例的答案,但是我需要的一個具體案例是將一些東西寫入流中,並在它們之間使用分隔符,就像stream.write(','.join(name_list)) ,但在一個for循環而不是連接字符串,因爲有很多寫道... – 2009-10-27 12:32:48

回答

93

大多數時候更容易(便宜),使第一迭代的特殊情況,而不是的最後一個:

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

沒錯,這種方式似乎比我的好,至少它不需要使用枚舉和len。 – 2009-10-27 12:11:30

+0

是的,但它增加了另一個'if',如果循環被分成兩個循環,則可以避免。但是,這僅在迭代大量數據列表時纔有意義。 – 2009-10-27 12:13:24

+0

分裂成兩個循環的問題是它違反DRY或它迫使你定義方法。 – 2009-10-27 12:15:26

10

如果你只是希望修改data_list最後一個元素,那麼你可以簡單地使用符號:

L[-1] 

但是,它看起來像你做的比這更多。你的方式沒有什麼錯。我甚至快速瀏覽了一下Django code的模板標籤,他們基本上是在做你正在做的。

+0

我沒有修改它,我正在使用它來執行某些操作 – 2009-10-27 12:20:10

+4

@ e.tadeu即使修改它也不重要。改變你的if語句爲:'如果data!= datalist [-1]:'並且保持其他所有內容都是我認爲最好的編碼方式。 – spacetyper 2015-08-28 20:29:56

+1

@spacetyper當最後一個值不唯一時會中斷。 – 2017-12-29 14:16:22

3

是否有可能遍歷所有 - 但最後一個元素,並處理循環之外的最後一個元素?畢竟,創建一個循環來完成類似於循環的所有元素的操作;如果一個元素需要特殊的東西,它不應該在循環中。

(也見這樣的問題:does-the-last-element-in-a-loop-deserve-a-separate-treatment

編輯:因爲這個問題是瞭解更多「在...之間」的,無論是第一元件處於特殊一個,它沒有前,或最後元素是特殊的,它沒有後繼者。

+0

但最後一個元素應該與列表中的其他每個元素類似。問題是應該只在*元素之間完成的事情。 – 2009-10-27 12:16:46

+0

在這種情況下,第一個是唯一沒有前任的人。除此之外,並循環列表的其餘部分的通用代碼。 – xtofl 2009-10-27 12:39:41

1

你的方式沒有任何問題,除非你有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 

。OUPUTS:

1 
2 
3 
3 

如果覺得我會那樣做,對我來說似乎很簡單。

+1

但是請注意,iterable [-1]不適用於所有* iterables(例如沒有__len__的生成器) – 2009-10-27 12:23:24

+0

對,我編輯它以添加我將使用自己的hack。不是最明亮的,但最簡單的。 – 2009-10-27 12:25:38

+0

如果您只想訪問循環後的最後一個項目,只需使用'item'而不是使用'list [-1]'重新計算它。但是,不過:我不認爲這是OP要求的,是嗎? – 2009-10-27 12:27:31

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

這太棒了。儘管你在yield語句中有一個多餘的冒號。 – 2017-11-07 17:48:00

14

在「之間的代碼」是頭 - 尾圖案的一個例子。

您有一個項目,後面是一系列(之間,項目)對。您還可以將其視爲一系列(項目,之間)對,後跟一個項目。將第一個元素作爲特殊元素和其他所有元素作爲「標準」情況通常比較簡單。

此外,爲避免重複代碼,您必須提供一個函數或其他對象來包含您不想重複的代碼。嵌入一​​個如果語句在一個總是假的循環中除了一次是愚蠢的。

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) 

這是更可靠的,因爲它是稍微容易證明,它不創建一個額外的數據結構(即一個列表的副本),並不需要很多的如果浪費執行除一次總是假的條件。

+2

函數調用比if語句慢,所以「浪費執行」參數不成立。 – 2009-11-04 18:41:45

+0

我不確定函數調用和if語句之間的速度差異與什麼有關。關鍵是,這個表達式沒有if語句,它總是假的(除了一次)。 – 2009-11-04 18:43:11

+1

我解釋了你的語句「...並且不需要大量浪費執行if條件,除了一次」as「之外總是假的...並且速度更快,因爲它節省了一些「如果」。顯然你只是提到「代碼清潔」? – 2009-11-04 18:52:02

0

假設輸入作爲一個迭代器,下面是使用三通和izip從itertools方式:

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

這類似於螞蟻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 
+0

我也喜歡這個解決方案! – 2009-10-28 11:58:08

7

雖然這個問題是很老,我來到這裏,通過谷歌,我發現了一個很簡單的方法:列出切片。假設你想在所有列表條目之間加上'&'。

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

此返回 '1 3'。

+3

您剛剛重新實現了連接函數:'「&」.join([x]中的[str(x)]] – 2016-01-21 16:45:41

+0

@BryanOakley這應該是一個真正的答案。我不知道這一點,它很好地回答了OP的問題。 – Coffeinated 2016-01-22 09:25:22

+0

字符串連接有點低效。如果在這個例子中'len(l)= 1000000',程序將運行一段時間。 'append'是推薦的afaik。 'L = [1,2,3]; l.append(4);' – plhn 2017-04-10 06:30:02

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可能會檢測到相同的元素併爲它們重用相同的對象。例如,對於具有相同值和普通整數的字符串。

0

,如果你正在經歷的名單,對於我這種工作太:

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

谷歌把我帶到這個老問題,我想我可以添加不同的方法解決這個問題。

這裏的大部分答案都會處理一個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

如果項目是唯一的:

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 
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 (1, 2, 3): 
...  pass 
... 
>>> i 
3 
相關問題