Computer368106 views
高校倫理1440106 views
高校物理160203 views
数学講師2886227 views
小学算数1200427 views
教育149510 views
高校日本史190571 views
いろは3010814 views
中学英語811710 views
高校化学2924392 views
Help
Tools
NewsSpreadsheetCalendarSlidesTier ListPen ToolIllustrationCrayonPixel ArtASCII ArtPerspectiveEndless StairsGraphMind MapER DiagramFamily TreeMemeCurved TextImage EditorMosaicRetro FilterPencil SketchOCR/HighlighterMakeup EditorFaviconVideo TrimmerScrolling VideoVideo TitleColor PickerColor ExtractorBonfireFireworksWater RippleWater SplashBreaking GlassWood GrainMarble TextureCSS ButtonIcon MakerBar ChartGrouped Bar ChartStacked Bar ChartPie ChartLine ChartArea ChartStacked Area ChartScatter Plot3D Bar Chart3D Pie ChartBar Chart RaceBubble ChartPopulation PyramidPictogramEarningsCandlestick ChartInvestment RiskMortgage SimulatorCalculatorMatrix CalculatorFunction GraphPolynomial ExpansionVenn DiagramField VisualizerRubik's Cube Group TheoryTraveling SalesmanVoronoi and DelaunayFractalColumn ArithmeticDraw Math FiguresArithmetic AnimationArithmetic Word ProblemsCounting with Tree DiagramsCube NetsCross SectionsMotion PathMechanicsWavesLight and LensesThermodynamicsHow Semiconductors WorkMolecular StructuresAtomic OrbitalsElectrochemical CellsChemical EquilibriumCrystal LatticesBuffer pHComplex IonsDNA Double HelixCell DivisionMembrane ChannelsNerve ImpulseRock ClassificationWeatherConstellationsSolar and Lunar Eclipses3D ModelingFloor PlanSeismic StructuresIntersection TurnMaglevCooking AnimationOrigamiLive Viewer CountGeoJSON MapRailway MapPopulation MapCrime MapLand Price MapSchool MapShrine and Castle MapHouse of Representatives MapWord MapSolitaireReversiHakoiri MusumeChessHamburgerRippleSlide Puzzle MakerNeon PinballNovel MakerJapanese Typing PracticePiano Score EditorMusic Theory

English

Python の throw() と close() でジェネレータを制御する

ジェネレータは yield で値を返すだけの存在ではない。外部からジェネレータに例外を投げ込んだり、明示的に終了させたりする仕組みが用意されている。throw() と close() がそれにあたる。

throw() の基本

throw() は、ジェネレータが yield で一時停止している箇所に、任意の例外を送り込むメソッドだ。ジェネレータの内部で try/except を使えば、その例外をキャッチして処理を続行できる。

def accumulator():
    total = 0
    while True:
        try:
            value = yield total
            if value is not None:
                total += value
        except ValueError:
            print("不正な値が送られたためリセットします")
            total = 0

gen = accumulator()
next(gen)          # ジェネレータを起動
gen.send(10)       # total = 10
gen.send(20)       # total = 30
gen.throw(ValueError)  # リセットされ total = 0
print(gen.send(5))     # 5

throw() の呼び出しによって、yield の位置で ValueError が発生する。ジェネレータ内部の except 節がこれを捕捉し、total を 0 にリセットしたうえで次の yield に進む。throw() の戻り値は、次の yield が返す値になる。

throw() の挙動を正確に理解する

throw() が呼ばれたとき、ジェネレータの内部では以下のことが起きている。

throw(例外) を呼び出す

yield の位置で指定した例外が発生する

ジェネレータ内の except で捕捉できれば次の yield まで進む

捕捉されなければ呼び出し元に例外が伝播する

捕捉されなかった場合の挙動も確認しておこう。

def simple_gen():
    yield 1
    yield 2
    yield 3

gen = simple_gen()
next(gen)  # 1

try:
    gen.throw(RuntimeError, "強制エラー")
except RuntimeError as e:
    print(f"捕捉: {e}")  # 捕捉: 強制エラー

simple_gen の内部には try/except がないため、throw() で投げた RuntimeError はそのまま呼び出し元に伝播する。ジェネレータはこの時点で終了し、以降 next() を呼ぶと StopIteration が発生する。

throw() の引数形式

throw() には 2 種類の呼び出し方がある。

例外インスタンスを渡す

gen.throw(ValueError(“メッセージ”)) のように、インスタンスを直接渡す形式。Python 3 ではこちらが推奨される。

例外クラスと引数を分けて渡す

gen.throw(ValueError, “メッセージ”) のように、クラスとメッセージを別々に渡す古い形式。Python 2 との互換性のために残っているが、新しいコードでは使わないほうがよい。

close() の基本

close() はジェネレータを明示的に終了させるメソッドだ。内部的には yield の位置に GeneratorExit 例外を送り込む。

def resource_gen():
    print("開始")
    try:
        while True:
            yield "データ"
    except GeneratorExit:
        print("クリーンアップ処理を実行")
    print("終了")

gen = resource_gen()
next(gen)    # "開始" と表示
gen.close()  # "クリーンアップ処理を実行" と "終了" が表示

close() を呼ぶと GeneratorExit がジェネレータ内で発生し、except GeneratorExit でリソースの後処理を行える。ジェネレータが無限ループであっても、close() で安全に停止できるわけだ。

GeneratorExit の特殊なルール

GeneratorExit には重要な制約がある。except GeneratorExit の中で yield を使ってはならない。

def bad_gen():
    try:
        yield 1
    except GeneratorExit:
        yield 2  # これは RuntimeError を引き起こす

gen = bad_gen()
next(gen)

try:
    gen.close()
except RuntimeError as e:
    print(f"エラー: {e}")

close() は「このジェネレータを終わらせる」という意図の操作であり、GeneratorExit を捕捉した後にさらに値を yield するのは矛盾している。Python はこの矛盾を RuntimeError として通知する。

GeneratorExit を捕捉する場合、後処理として許されるのはログ出力やファイルのクローズなどの副作用のない終了処理に限られる。

yield や send() で再び値をやり取りしようとする操作は禁止されている。

try/finally との組み合わせ

GeneratorExit をわざわざ except で捕捉しなくても、finally 節でクリーンアップするのがより一般的なパターンだ。

def file_reader(path):
    f = open(path)
    try:
        for line in f:
            yield line.strip()
    finally:
        f.close()
        print(f"{path} をクローズしました")

gen = file_reader("example.txt")
# 数行だけ読んで途中で終了
for i, line in enumerate(gen):
    if i >= 3:
        gen.close()
        break

finally は GeneratorExit の有無にかかわらず実行されるため、リソース解放の記述場所として適切だ。ジェネレータがガベージコレクションで回収される際にも finally 節は実行される。

throw() と close() の使い分け

throw()

ジェネレータに異常状態を伝え、回復可能な処理を促す。入力バリデーションの失敗や再試行可能なエラーの通知に使う。ジェネレータは例外を捕捉すれば処理を続行できる。

close()

ジェネレータを終了させる。不要になったジェネレータのリソースを解放する用途で使う。GeneratorExit を受け取ったジェネレータは新しい値を yield できない。

実用例: タイムアウト付きデータ処理

throw() と close() を組み合わせた実践的な例を示す。一定時間で処理を打ち切り、クリーンアップを行うパターンだ。

import time

class TimeoutError(Exception):
    pass

def data_processor():
    processed = 0
    try:
        while True:
            try:
                data = yield processed
                # データ処理のシミュレーション
                time.sleep(0.1)
                processed += 1
            except TimeoutError:
                print(f"タイムアウト: {processed} 件処理済み")
                yield processed
                return
    finally:
        print(f"終了: 合計 {processed} 件を処理")

gen = data_processor()
next(gen)

start = time.time()
for i in range(100):
    if time.time() - start > 0.5:
        result = gen.throw(TimeoutError)
        print(f"結果: {result} 件")
        break
    gen.send(f"item_{i}")
else:
    gen.close()

TimeoutError を throw() で送り込むと、ジェネレータは処理済み件数を返してから自ら return で終了する。時間内にすべて完了した場合は close() で正常終了させる。どちらのケースでも finally 節のクリーンアップは確実に実行される。

throw() でジェネレータに例外を送り、ジェネレータ内部で捕捉されなかった場合、何が起こるか?

  • ジェネレータが無視して次の yield に進む
  • StopIteration が発生する
  • 例外が呼び出し元に伝播し、ジェネレータは終了する
  • GeneratorExit が自動的に発生する
__RESULT__

throw() で送った例外がジェネレータ内で捕捉されない場合、その例外はそのまま throw() の呼び出し元に伝播する。ジェネレータはその時点で終了状態になる。

close() を呼んだとき、except GeneratorExit の中で yield を使うとどうなるか?

  • 正常に次の値が返される
  • GeneratorExit が再送される
  • RuntimeError が発生する
  • StopIteration が発生する
__RESULT__

close() の目的はジェネレータの終了であり、GeneratorExit を捕捉した後に yield するのは矛盾する操作として Python が RuntimeError を送出する。