Python 如何在发电机中检测它是否被外部中断

Python 如何在发电机中检测它是否被外部中断,python,exception,generator,Python,Exception,Generator,我有这个发电机: def gen(): rounds = 0 for x in xrange(10): rounds += 1 yield x print 'Generator finished (%d rounds)' % (rounds) 如果我通常称之为: for x in gen(): pass 我得到了预期的结果: Generator finished (10 rounds) 但是如果我中断发电机,我什么也得不到:

我有这个发电机:

def gen():
    rounds = 0
    for x in xrange(10):
        rounds += 1
        yield x
    print 'Generator finished (%d rounds)' % (rounds)
如果我通常称之为:

for x in gen():
    pass
我得到了预期的结果:

Generator finished (10 rounds)
但是如果我中断发电机,我什么也得不到:

for x in gen():
    break
我想得到:

Generator interrupted (1 rounds)

是否有可能在发电机内部检测到发电机已从外部中断?是否有任何异常可以捕获以检测此事件?

您不能,因为生成器的默认行为将一直被中断。生成器每次生成值时都会持续暂停。

换句话说,您对发电机工作原理的理解是不正确的。发电机上的回路完全不受发电机控制;它的任务就是生成下一个值,该值将取消暂停代码,直到执行下一个
yield
表达式

因此,当没有请求下一个值时,生成器将暂停,无法执行代码“检测”它没有被请求另一个值

调用生成器函数时会发生以下情况:

  • 退回一张特价票。不执行函数体中的任何代码
  • 生成器迭代器之外的代码可能使用它作为迭代器,也可能不使用它。每次需要新值时,都会调用
  • 调用
    .next()
    方法时,函数体将运行,直到遇到
    yield
    表达式。暂停函数体,并将
    yield
    表达式的结果作为
    .next()
    方法的结果返回
您可以使用和方法在生成器函数中显式发送消息或引发异常,但在不迭代生成器对象时不会发送此类消息或异常

您可以查找的一件事是当生成器对象关闭时在生成器函数中抛出的
GeneratorExit
异常;见:

在生成器功能暂停的点处引发一个
GeneratorExit

当生成器对象被垃圾收集时(例如,不再有任何对象引用它),将自动调用
generator\u object.close()
方法:

def gen():
    rounds = 0
    for x in xrange(10):
        rounds += 1
        try:
            yield x
        except GeneratorExit:
            print 'Generator closed early after %d rounds' % rounds
            raise
    print 'Generator finished (%d rounds)' % rounds
演示:


这仅仅是因为返回的生成器对象只被
for
循环引用,并且在
for
循环结束时被获取。

A
contextmanager
允许您提供要在生成器退出时执行的代码

from contextlib import contextmanager
rounds = 0

@contextmanager
def on_generator_exit():
    try:
        yield
    finally:
        global rounds
        print 'Generator finished (%d rounds)' % (rounds)

def gen():
    with on_generator_exit():
        global rounds
        rounds = 0
        for x in xrange(10):
            rounds += 1
            yield x

for x in gen():
    break
输出:

Generator finished (1 rounds)

我怀疑是否有
例外情况
。所发生的一切是,只在一次调用之后,函数就停止被调用。因为这样的控制永远不会返回到函数来执行
打印
。不,这里没有例外-您只是停止了对它的循环。您可以在外部处理它,方法是保留对生成器的引用,并确保
next(gen)
引发
StopIteration
,但无法从内部判断-生成器只是等待再次调用或垃圾回收。这不是您要求的,但您可以将生成器包装到上下文管理器中,使用
\uuuuuuuuuuuuuuuuu
——类似于:我认为
GeneratorExit
对于我正在寻找的东西来说已经足够好了。我想,如果我有一个对生成器的引用,它将不会像预期的那样工作,但是对于简单的情况,只要在它上循环,就会始终产生预期的结果。@jeckyll2hide:考虑到生成器关闭时依赖于Python实现。CPython使用引用计数,生成器对象立即被捕获,但在Jython或IronPython中,在对象不再被引用和垃圾收集清除之间可能存在明显的延迟。
Generator finished (1 rounds)