2010-08-15 24 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() 

我个人比较喜欢的第一个样式。因人而异。