是否有更好的方法来吞下python中yield产生的StopIteration异常?

时间:2014-06-16 06:46:47

标签: python yield yield-keyword

现在我计划在python中了解有关yield的更多信息。我找到了一些关于yield的代码,它实现了算法Reservoir Sampling如下:

def RandomSelect(knum, rand=None):
    ''' (int, func) -> list

    Reservoir Sampling implementation
    '''
    selection = None
    k_elems_list = []
    count = 0

    if rand is None:
        rand = Random()

    while True:
        item = yield selection
        if len(k_elems_list) < knum:
            k_elems_list.append(item)
        else:
            # Randomly replace elements in the reservoir with a decreasing probability
            r = rand.randint(0, count)
            if r < knum:
                k_elems_list[r] = item
        count += 1
    print k_elems_list

为了打破while循环,我只需在item = yield selection之后添加一些代码

        if item == -1: # reach to the end of data, just break
            break

问题1 ,有没有更好的方法来打破while循环?

调用函数RandomSelect

myList = [1,2,3,4,5,6,7,8,-1]
cr = RandomSelect(3);
cr.next() # advance to the yield statement, otherwise I can't call send
try:
    for val in myList:
        cr.send(val)
except StopIteration:
    pass
finally:
    del cr

我必须明确地捕获StopIteration异常。

问题2 ,有没有更好的方法来吞下代码中的StopIteration

1 个答案:

答案 0 :(得分:3)

我认为一种稍微更简洁的方法来完成正在完成的工作 - 它解决了你的两个问题 - 将通过调用它的close()方法来终止它并突破循环来显式关闭生成器。这样做也意味着StopIteration不需要吞下&#34;。另一个好处是,不再需要在列表末尾添加-1哨兵值。

def RandomSelect(knum, rand=None):
    ''' (int, func) -> list

    Reservoir Sampling implementation
    '''
    selection = None
    k_elems_list = []
    count = 0
    if rand is None:
        rand = Random()   

    while True:
        try:
            item = yield selection
        except GeneratorExit:
            break
        if len(k_elems_list) < knum:
            k_elems_list.append(item)
        else:
            # Randomly replace elements in the reservoir with a decreasing probability
            r = rand.randint(0, count)
            if r < knum:
                k_elems_list[r] = item
        count += 1
    print k_elems_list

myList = [1,2,3,4,5,6,7,8]
cr = RandomSelect(3)
cr.next() # advance to the yield statement, otherwise I can't call send
for val in myList:
    cr.send(val)
cr.close()    
del cr 

一个小小的额外增强功能(关于你没有问过的内容)就是这样做,因此在调用yield之前手动前进到send()语句是不必要的。 。实现这一目标的一个好方法是使用类似于名为consumer() David Beazley在他的 Generator Tricks中描述的装饰函数 适用于PyCon 2008的系统程序员 presentation

def coroutine(func):
    """ Decorator that takes care of starting a coroutine automatically. """
    def start(*args, **kwargs):
        cr = func(*args, **kwargs)
        cr.next()
        return cr
    return start

@coroutine
def RandomSelect(knum, rand=None):
          .
          .
          .
    print k_elems_list

myList = [1,2,3,4,5,6,7,8]
cr = RandomSelect(3)
#cr.next() # NO LONGER NECESSARY
for val in myList:
    cr.send(val)
cr.close()    
del cr