用于测试发电机是否“正在运行”和/或已经用尽的标志

时间:2017-08-07 14:58:41

标签: python generator

摘要

我所追求的是一种确定发电机是否“正在运行”的方法。

详细

我对“当前正在运行”的定义是:

  1. 生成器代码已经开始执行(即,next.send(None)或其他一些迭代操作,例如for循环,先前已在生成器上执行,AND
  2. 发生器以前没有耗尽/关闭(再次请注意,对于上面的生成器,.throw(some_error)被捕获并且不会耗尽生成器,因为一些新循环已启动;没有循环或yield from处理过的错误通常会耗尽发电机。)
  3. 我知道没有办法(或者至少是一种简单的方法)在生成器中“窥视”并找出它是否会在下一次迭代中引发StopIteration。这与发电机的性质是对立的,并且 NOT 是我追求的。

    实施例

    假设我有一些生成器,g由某个生成器函数f创建:

    def f():
        '''Lots of 1s.'''
        print('I am now running.')
        while True:
            try:
                signal = yield 1
                if signal:
                    break
            except GeneratorExit:
                raise #  important so that closing the generator doesn't cause a RuntimeError
            except Exception:
                print('caught exception!')
    

    我们可以通过这种方式“初始化”生成器:

    >>> g = f()
    >>> next(g)
    I am now running.
    1
    
    # OR:
    
    >>> g.send(None) #  equivalent to next(g)
    I am now running.
    1
    

    如果我将任何真相发送到生成器,则会引发StopIteration错误(例如g.send('foo'))。或者,如果我拨打g.close(),然后执行next(g),我也会获得StopIteration。如果我向g.throw(e)抛出任何错误,则生成器将继续。这一切都符合预期。

    我希望能够做的是以下内容,它显示此生成器是否正在运行:

    >>> g=f()
    >>> g.running
    False
    >>> g.send(None)
    I am now running.
    1
    >>> g.running
    True
    >>> g.close()
    >>> g.running
    False
    

    我在答案中提供了一种可能的方法。但是,我认为必须有更好的方法。

2 个答案:

答案 0 :(得分:5)

inspect.getgeneratorstate告诉您发电机的状态:

>>> import inspect
... 
... def gen():
...     yield 1
...     
... g = gen()
...
>>> inspect.getgeneratorstate(g)
'GEN_CREATED'
>>> next(g)
1
>>> inspect.getgeneratorstate(g)
'GEN_SUSPENDED'
>>> next(g)
>>> inspect.getgeneratorstate(g)
'GEN_CLOSED'

答案 1 :(得分:0)

我唯一的想法是某种生成器包装器,其方法可以委托给存储的生成器,但这有点笨拙。它可能看起来像这样(没有经过测试):

class MyGen():
    '''Generator wrapper with 'exhausted' and 'running' flags.'''
    def __new__(cls, some_func):
        cls._func = some_func
        return super().__new__(cls)
    def __call__(self, *args, **kwargs):
        self.exhausted = False
        self.running = False
        self.start(*args, **kwargs)
        return self
    def start(self, *args, **kwargs):
        self._gen = self._func(*args, **kwargs)
    def __next__(self):
        try:
            self.running = True
            next(self._gen)
        except StopIteration:
            self.exhausted = True
            self.running = False
            raise
    def send(self, *args, **kwargs):
        try:
            self.running = True
            self._gen.send(*args, **kwargs)
        except StopIteration:
            self.exhausted = True
            self.running = False
            raise
    def throw(self, *args, **kwargs):
        try:
            self._gen.throw(*args, **kwargs)
        except StopIteration:
            self.exhausted = True
            self.running = False
            raise
    def close(self):
        self._gen.close()
        self.exhausted = True
        self.running = False

您甚至可以将其用作装饰者:

@MyGen
def f():
    yield 1

我真的不喜欢这样,因为看起来真的应该有一个现有的地方来寻找一个耗尽的发电机"或"发电机运行"旗。