0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

GeneratorExitを捕捉してジェネレータ側からイテレーションの終了を検知する

Posted at

イテレータそのものはbreak等で最後まで回らない可能性があるけど、ジェネレータ内部のイテレーションは最後まで回したいという場合がまれにある。

うまくいかない例

例として次のrange_printsumrange関数にその範囲の総和をprintする処理を追加したものだ。

gen.py
def range_printsum(*args, **kwargs):
    sum_ = 0

    for i in range(*args, **kwargs):
        sum_ += i
        yield i

    print(sum_)

このジェネレータ実装ではイテレーションを途中で止めてしまうと、sum_に格納されるのは実際にyieldした数の合計だし、そもそもsum_は出力されない。

>>> from gen import range_printsum
>>> for i in range_printsum(11):
...     pass
...
55
>>> for i in range_printsum(11):
...     if i > 5:
...         break
...
>>>

うまくいく例

そこでGeneratorExitを捕捉する。これはジェネレータが閉じられた(ジェネレータのclose()が呼ばれた)時に送出される。これはfor等のループが中断された場合でも捕捉できる。
GeneratorExitを捕捉した後は値を返さず、加算だけ行うようrange_printsumを修正する。

gen.py
def range_printsum(*args, **kwargs):
    sum_ = 0
    stop_yield = False

    for i in range(*args, **kwargs):
        sum_ += i
        if not stop_yield:
            try:
                yield i
            except GeneratorExit:
                stop_yield = True

    print(sum_)

今度は期待通りに動く。

>>> from gen import range_printsum
>>> for i in range_printsum(11):
...     pass
...
55
>>> for i in range_printsum(11):
...     if i > 5:
...         break
...
55
>>>

このように中断しても動く。

>>> try:
...     for i in range_printsum(11):
...         if i > 5:
...             raise IndexError
... except IndexError:
...     pass
...
55
>>>

ジェネレータの仕様による注意点として、一度閉じられたジェネレータは値を生成してはならない。GeneratorExitを捕捉した後に何らかの値をyieldするとRuntimeError: generator ignored GeneratorExitというエラーが発生する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?