1.3 例外

Pythonプログラマは、特定のエラー処理が要求される場合だけ 例外を扱う必要がある。扱われなかった例外は自動的に呼び出し側に、 そして呼び出し側の呼び出し側に、などと最上位のインタープリタ に到達するまで伝播する。インタープリタではエラーが スタックトレースバックを伴なってユーザに報告される。

しかしながら、Cプログラマにとっては、エラー検査は常に明示的で ある必要がある。 関数の説明の中で明示的に主張されない限り、 Python C/API中の全ての関数は例外を起こすことができる。 一般に、ある関数がエラーに遭遇したとき、関数は例外を設定して、 所有するオブジェクトへの参照を捨てて、エラーの指標を返す -- 指標は通常はNULLもしくは-1である。 いくつかの関数は、ブール型の真偽の結果を、エラーを示す偽の 値で返す。明示的なエラーの指標を返さなかったり曖昧な戻り値を持ち、 PyErr_Occurred()で 明示的なエラーの検査を必要とする関数はほとんどない。

例外の状態はスレッドごとの記憶域(これはスレッド化されていない アプリケーションで大域的な記憶域を使うことと同じである)で 維持される。 スレッドは2つの状態のうちの1つでいることができる: 例外が 起こったか、そうでないかである。 これを調べるために、関数PyErr_Occurred()を 使うことができる: その関数は例外が起こったときに、例外の 型オブジェクトへの借りものの参照を、そうでなければNULLを返す。 例外の状態を設定するいくつかの関数がある: PyErr_SetString()は 例外の状態を設定するための最もありふれた(しかしながら 最も一般的ではないが)関数である。 そして、PyErr_Clear()は 例外の状態を取り除く。

完全な例外の状態は3つのオブジェクト(それら全てはNULLであり得る) から成る: 例外の型、対応する例外の値、そしてトレースバックである。 これらはPythonの オブジェクトsys.exc_typesys.exc_valuesys.exc_tracebackと同じ意味を持つ。 しかしながら、それらは同じではない: そのPythonのオブジェクトは Pythonのtry ... except文で扱われた 最後の例外を表わし、一方C言語水準での例外の状態は、 例外がPythonバイトコードのインタープリタのメインループに到達する まで、C言語関数の間で渡されている間存在するだけである。 このメインループでは、その例外をsys.exc_typeとその仲間に 移す処理を行う。

Python 1.5から始まった、Pythonのコードから例外状態に アクセスするためのより好ましくスレッドで安全な方法は、関数 sys.exc_info()を呼ぶことである。その関数は、 Pythonのコードのためにスレッドごとの例外の状態を返す。 また、例外を捉える関数が、その呼び出し側の例外の状態を 保つためにそのスレッドの例外の状態を保存して元に戻すように、 例外の状態にアクセスする両方の方法の意味が変わっている。 これは、無害に見える関数が扱っている例外を上書きすること によって起こされる、例外を扱うコード中のよくあるバグを防ぐ。 それはまた、トレースバック中のスタックフレームによって参照される オブジェクトの寿命が望まれないのに延びる、その延びを短くする。

一般原則として、ある仕事を行うために別の関数を呼ぶ関数は、 呼び出した関数が例外を起こしたかどうか調べて、もしそうであれば、 その関数の呼び出し側に例外の状態を渡すべきである。 それが所有するオブジェクトへのどんな参照も捨てて、エラーの指標を 返すべきである。しかし、別の例外を設定すべきではない -- それはちょうど起きた例外を上書きして、エラーの正確な原因についての 重要な情報を失うだろう。

例外を検出してそれらを渡す単純な例題が、 上記の例題sum_sequence() で示されている。 その例題は、エラーを検出したときに、所有するどの参照もたまたま 片付ける必要がない。次の例題の関数はいくつかエラーの後片付けを 示している。第一に、あなたがPythonをなぜ好きか思い出させるために、 等価なPythonのコードを示す:

def incr_item(dict, key):
    try:
        item = dict[key]
    except KeyError:
        item = 0
    return item + 1

ここに対応するC言語のコードを、きちんとした形で示す。

int incr_item(PyObject *dict, PyObject *key)
{
    /* Objects all initialized to NULL for Py_XDECREF */
    PyObject *item = NULL, *const_one = NULL, *incremented_item = NULL;
    int rv = -1; /* Return value initialized to -1 (failure) */

    item = PyObject_GetItem(dict, key);
    if (item == NULL) {
        /* Handle KeyError only: */
        if (!PyErr_ExceptionMatches(PyExc_KeyError)) goto error;

        /* Clear the error and use zero: */
        PyErr_Clear();
        item = PyInt_FromLong(0L);
        if (item == NULL) goto error;
    }

    const_one = PyInt_FromLong(1L);
    if (const_one == NULL) goto error;

    incremented_item = PyNumber_Add(item, const_one);
    if (incremented_item == NULL) goto error;

    if (PyObject_SetItem(dict, key, incremented_item) < 0) goto error;
    rv = 0; /* Success */
    /* Continue with cleanup code */

 error:
    /* Cleanup code, shared by success and failure path */

    /* Use Py_XDECREF() to ignore NULL references */
    Py_XDECREF(item);
    Py_XDECREF(const_one);
    Py_XDECREF(incremented_item);

    return rv; /* -1 for error, 0 for success */
}

この例はC言語でのgoto文の推奨される使い方を示している。 それは特定の例外を扱うための PyErr_ExceptionMatches()PyErr_Clear()の使い方を、 そしてNULLかもしれない所有された参照を捨てるための Py_XDECREF()の使い方を 例示している(名前中の"X"に注意せよ。 NULLの参照にでくわしたら Py_DECREF()はクラッシュするだろう)。 これが働くために、所有された参照を保持するために使われる変数が NULLに初期化されることが重要である。 同様に、返すつもりの戻り値は-1 (失敗)に初期化され、 最後の呼び出しが成功してから成功に設定されるだけである。