2010-08-15 35 views
5

假設我正在循環一個迭代器,並且想要在迭代器爲空時採取一些操作。我能想到的做到這一點,兩個最好的方法是:嘗試循環空迭代時採取行動的習慣方式

for i in iterable: 
    # do_something 
if not iterable: 
    # do_something_else 

empty = True 
for i in iterable: 
    empty = False 
    # do_something 
if empty: 
    # do_something_else 

首先依賴於可迭代是一個集合(所以沒用當迭代被傳遞到了函數/方法,其中循環是),第二次設置每循環看起來很醜陋的每個循環。

有沒有另一種方法,我失蹤或是第二個選擇最好?這將是非常酷,如果有一些條款,我可以添加到循環語句,將爲我處理這很像else使not_found旗幟消失。


我不是在尋找聰明的黑客。

我不是在尋找涉及大量的代碼

我要尋找一個簡單的語言功能的解決方案。 我在尋找一個明確pythonic方式來迭代一個迭代,並採取一些行動,如果可迭代是空的,任何有經驗的Python程序員都會明白。如果我可以做到這一點,而不需要在每次迭代中設置一個標誌,那就太棒了。 如果沒有這樣的簡單習語,那麼就忘了它。

+0

這當然不是很重要,但我相信你的例子中的評論應該讀取'a <= x Bolo 2010-08-15 06:31:53

+0

@Bolo好看。 – aaronasterling 2010-08-15 06:46:51

+0

另請參閱:http://stackoverflow.com/questions/661603/how-do-i-know-if-a-generator-is-empty-from-the-start和http://stackoverflow.com/questions/ 1966591/hasnext-in-python-iterators – 2010-08-15 07:01:04

回答

3

我覺得這個最乾淨的方式做到這一點:

# first try with exceptions 
def nonempty(iter): 
    """ returns `iter` if iter is not empty, else raises TypeError """ 
    try: 
     first = next(iter) 
    except StopIteration: 
     raise TypeError("Emtpy Iterator") 
    yield first 
    for item in iter: 
     yield item 


# a version without exceptions. Seems nicer: 
def isempty(iter): 
    """ returns `(True,())` if `iter` if is empty else `(False, iter)` 
     Don't use the original iterator! """ 
    try: 
     first = next(iter) 
    except StopIteration: 
     return True,() 
    else: 
     def iterator(): 
      yield first 
      for item in iter: 
       yield item 
     return False, iterator() 



for x in ([],[1]): 
    # first version 
    try: 
     list(nonempty(iter(x))) # trying to consume a empty iterator raises 
    except TypeError: 
     print x, "is empty" 
    else: 
     print x, "is not empty" 

    # with isempty 
    empty, it = isempty(iter(x)) 
    print x, "is", ("empty" if empty else "not empty") 
+0

+1。這是迄今爲止最好的答案。我可能最終會接受這個。 – aaronasterling 2010-08-15 17:44:59

3

這是相當的hackish,但可以刪除i,然後檢查它是否在循環後存在(如果不是,則循環永遠不會發生):

try: 
    del i 
except NameException: pass 

for i in iterable: 
    do_something(i) 

try: 
    del i 
except NameException: 
    do_something_else() 

我認爲這可能比只用一個標誌醜陋雖然

+1

這很聰明,但正如你對上一條語句所期待的那樣,這不是我正在尋找的。這就是說,聰明總是值得+1 – aaronasterling 2010-08-15 05:11:30

2

更新2

我喜歡Odomontois' answer。恕我直言,它比我下面寫的更適合這個問題。

更新

(讀取OP的評論和編輯問題之後)你可以做到這一點。請看下圖:

def with_divisible(n, a, b, f): 
it = (i for i in xrange(a, b) if not i % n) 
for i in wrapper(it): 
    f(i) 

>>> with_divisible(1, 1, 1, lambda x: x) 
Traceback (most recent call last): 
    File "<pyshell#55>", line 1, in <module> 
    with_divisible(1, 1, 1, lambda x: x) 
    File "<pyshell#54>", line 3, in with_divisible 
    for i in wrapper(it): 
    File "<pyshell#46>", line 4, in wrapper 
    raise EmptyIterableException("Empty") 
EmptyIterableException: Empty 

>>> with_divisible(7, 1, 21, lambda x: x) 
7 
14 
...Snipped... 
    raise EmptyIterableException("Empty") 
EmptyIterableException: Empty 

原來的答案

有趣的問題。我做了一些實驗,並與下面上來:

class EmptyIterableException(Exception): 
    pass 

def wrapper(iterable): 
    for each in iterable: 
     yield each 
    raise EmptyIterableException("Empty") 

try: 
    for each in wrapper(iterable): 
     do_something(each) 
except EmptyIterableException, e: 
    do_something_else() 
+0

抱歉,我的問題措辭不佳。我要編輯我的問題。 – aaronasterling 2010-08-15 06:11:11

+0

我搞砸了我的編輯。雖然,這不應該把你拋棄。 'for'循環中的行讀取'empty = False'。無論如何,你的代碼都會引發異常。它的工作原理是,如果我將'empty'標誌移動到包裝器中,並在包裝​​器中的'if empty'上引發異常。對不起,我很挑剔,但我很難把解決方案稱爲慣用的。儘管我確實喜歡它。 – aaronasterling 2010-08-15 06:33:02

+0

@aaronasterling:你的表達式返回一個生成器對象。它有助於調用發生器上的next()並捕獲'StopIteration'? – 2010-08-15 06:36:43

0

什麼扭轉「如果」和「對」:

if iterable: 
    for i in iterable: 
     do_something(i) 
else: 
    do_something_else() 

OK,這也不行!

這裏有一個其他的解決方案:http://code.activestate.com/recipes/413614-testing-for-an-empty-iterator/

+3

嘗試'iterable = iter([])'。 – kennytm 2010-08-15 06:56:52

+2

KennyTM說什麼。一個簡單的布爾測試將會失敗並帶有生成器。 – 2010-08-15 07:15:00

+0

啊......謝謝,我想知道爲什麼每個人都寫這麼複雜的代碼。 – Eike 2010-08-15 07:31:22

2
if not map(do_something_callable,iterable) : 
    # do something else 
+0

這甚至開始解決這個問題呢? – aaronasterling 2010-08-15 07:43:18

+0

其實這可能值得一試。映射調用'map(f,divisibles(n,a,b))'將函數f映射到序列'divisibles'上。如果'可分割'是空的(我相信這是你試圖測試的),'map'的結果將是一個空列表,因此第2行會引發異常。 但是,如果'divisibles'不是空的,'map'將產生'f'映射到'divisible'上的值。 – 2010-08-15 11:30:07

+0

不,這不值得一試。它適用於我發佈的特定示例。我在詢問有關更廣泛的案例是無用的。請刪除此答案。 – aaronasterling 2010-08-15 16:51:37

0

這是Michael Mrozek組合的和FM的答案:

def with_divisible(n, a, b, f): 
    '''apply f to every integer x such that n divides x and a <= x < b''' 
    it = (i for i in xrange(a, b) if not i % n) 
    for i in it: 
     f(i) 
    try: i   # test if `it` was empty 
    except NameError: print('do something else') 

def g(i): 
    print i, 

with_divisible(3, 1, 10, g) # Prints 3 6 9. 
with_divisible(33, 1, 10, g) # Prints "do something else" 
0

發電機有'gi _frame'屬性,一旦生成器耗盡,該屬性就爲None,但只有在StopIteration被引發之後。如果這是可以接受的,這裏的東西,你可以嘗試:

import types 

def do(x, f, f_empty): 
    if type(x) == types.GeneratorType: 
     # generators have a 'gi_frame' property, 
     # which is None once the generator is exhausted 
     if x.gi_frame: 
      # not empty 
      return f(x) 
     return f_empty(x) 
    if x: 
     return f(x) 
    return f_empty(x) 

def nempty(lst): 
    print lst, 'not empty' 

def empty(lst): 
    print 'Twas empty!' 

# lists 
do([2,3,4], nempty, empty) 
do([], nempty, empty) 

# generators 
do((i for i in range(5)), nempty, empty) 
gen = (i for i in range(1)) 
gen.next() 
try: 
    gen.next() 
except StopIteration: 
    pass 
do(gen, nempty, empty) 
1

前進的一般方式,如果一個迭代是被消耗之前部分檢查是使用itertools.tee。通過這種方式,我們可以有兩個迭代器副本,並檢查一個虛擬空間,同時仍然從一開始就消耗另一個副本。

from itertools import tee 
it1, it2 = tee(iterable) 
try: 
    it1.next() 
    for i in it2: 
     do_some_action(i) #iterator is not empty 
except StopIteration: 
    do_empty_action() #iterator is empty 

StopIteration例外勢必會調用it1.next()的結果,提出了循環內弗魯姆將終止循環任何StopIteration例外。

編輯:對於那些誰不喜歡這樣的例外,islice可以用來建立一個單一的步驟循環:

from itertools import tee, islice 
it1, it2 = tee(iterable) 
for _ in islice(it1, 1): 
    #loop entered if iterator is not empty 
    for i in it2: 
     do_some_action(i) 
    break #if loop entered don't execute the else section 
else: 
    do_empty_action() 

我個人比較喜歡的第一個樣式。因人而異。