是否有Python函数检查生成器是否已启动?

时间:2013-07-16 19:03:05

标签: python generator

我尝试定义一个生成器函数mycount(),可以使用生成器函数send(0)重置,如下例所示。一切正常,除非我在尚未启动的新生成器对象上使用send(0)。在这种情况下,它会给出TypeError。是否有任何函数检查生成器是否已启动,或者是否必须捕获TypeError并在这种情况下使用mycount(0)创建新的生成器对象?

def mycount(value):
    while True:
        v = yield value
        if v == None:
            value = value + 1
        else:
            value = v

g = mycount(3)
print(next(g))    # prints 3
print(next(g))    # prints 4
print(g.send(0))  # prints 0
print(next(g))    # prints 1
print(next(g))    # prints 2

g2 = mycount(3)
g2.send(0)
# TypeError: can't send non-None value to a just-started generator

4 个答案:

答案 0 :(得分:8)

为避免向刚刚启动的生成器发送非None值,您需要先调用nextsend(None)。我同意其他人David Beazley的coroutine decorator(在python 3.x中你需要调用__next__()函数而不是next())是一个很好的选择。虽然那个特殊的装饰器很简单,但我也成功地使用了copipes库,这是Beazley演示中许多实用程序的很好实现,包括协程。

关于是否可以检查生成器是否已启动 - 在Python 3中,您可以使用inspect.getgeneratorstate。这在Python 2中不可用,但是CPython implementation是纯Python,并且不依赖于Python 3的任何新东西,所以你可以用同样的方式检查自己:

if generator.gi_running:
    return GEN_RUNNING
if generator.gi_frame is None:
    return GEN_CLOSED
if generator.gi_frame.f_lasti == -1:
    return GEN_CREATED
return GEN_SUSPENDED

具体而言,如果g2启动inspect.getgeneratorstate(g2) != inspect.GEN_CREATED

答案 1 :(得分:1)

由于您的错误意味着必须在刚刚启动的生成器上使用send调用None函数 (docs-link)

可以抓住TypeError并从那里滚动:

    #...
    try:
        g2.send(0)
    except TypeError:
        #Now you know it hasn't started, etc.
        g2.send(None)

无论哪种方式它都不能用于“重置”发生器,它只需要重新制作。

生成器概念和语法here的精彩概述,涵盖了生成器和其他高级主题的链接。

答案 2 :(得分:1)

特别是,您可能会找到一种方法来使用第2页中描述的consumer装饰器。大卫比兹利的“发电机技巧”I-131,J. Gwyn提供了一个链接:

def consumer(func):
    def start(*args,**kwargs):
        c = func(*args,**kwargs)
        c.next()
        return c
    return start

我在代码中使用类似的东西。

请注意,if v is None优先于if v == None

答案 3 :(得分:0)

这是一个完整的Python2兼容程序实现,getgeneratorstate(gtor),带有测试代码。

import unittest
import enum

class GtorState(enum.Enum):
    GEN_RUNNING ='GEN_RUNNING'
    GEN_CLOSED ='GEN_CLOSED'
    GEN_CREATED ='GEN_CREATED'
    GEN_SUSPENDED ='GEN_SUSPENDED'

    @staticmethod
    def getgeneratorstate(gtor):
        if gtor.gi_running:
            return GtorState.GEN_RUNNING

        if gtor.gi_frame is None:
            return GtorState.GEN_CLOSED

        if gtor.gi_frame.f_lasti == -1:
            return GtorState.GEN_CREATED

        return GtorState.GEN_SUSPENDED
    #end-def

def coro000():
    """ a coroutine that does little 

    """ 
    print('-> coroutine started')
    x =yield
    print('-> coroutine received ', x)


class Test_Coro(unittest.TestCase):
    def test_coro000(self):

        my_coro000 =coro000()
        self.assertEqual( GtorState.getgeneratorstate(my_coro000), GtorState.GEN_CREATED)

        next(my_coro000)  # prints '-> coroutine started'
        self.assertEqual( GtorState.getgeneratorstate(my_coro000), GtorState.GEN_SUSPENDED)

        try:
            my_coro000.send(42)  # prints '-> coroutine received 42 
            self.assertEqual( GtorState.getgeneratorstate(my_coro000), GtorState.GEN_SUSPENDED)

            self.fail('should have raised StopIteration ')

        except StopIteration:
            self.assertTrue(True, 'On exit a coroutine will throw StopIteration')
            self.assertEqual( GtorState.getgeneratorstate(my_coro000), GtorState.GEN_CLOSED)