#
2 幕間小話: エラーと例外

Python インタプリタ全体を通して、一つの重要な取り決めがあります: それは、関数が処理に失敗した場合、例外状態をセットして、 エラーを示す値 (通常は `NULL` ポインタ) を返さねばならない、 ということです。 例外はインタプリタ内の静的なグローバル変数に保存されます; この値が `NULL` の場合、例外は何も起きていないことになります。 第二のグローバル変数には、例外の ``付属値 (associated value)'' (`raise` 文の第二引数) が入ります。 第三の値には、エラーの発生源が Python コード内だった場合に スタックトレースバック (stack traceback) が入ります。 これらの三つの変数は、それぞれ Python の変数 `sys.exc_type`、 `sys.exc_value` および `sys.exc_traceback` と等価な C の変数です (Python ライブラリリファレンス の`sys` モジュールに関する節を参照してください。) エラーがどのように受け渡されるかを理解するには、これらの変数に ついてよく知っておくことが重要です。

Python API では、様々な型の例外をセットするための関数をいくつか 定義しています。

もっともよく用いられるのは`PyErr_SetString()` です。 引数は例外オブジェクトと C 文字列です。例外オブジェクトは 通常、`PyExc_ZeroDivisionError` のような定義済みのオブジェクト です。 C 文字列はエラーの原因を示し、Python 文字列オブジェクトに変換されて 例外の ``付属値'' に保存されます。

もう一つ有用な関数として`PyErr_SetFromErrno()` があります。 この関数は引数に例外だけをとり、付属値はグローバル変数 `errno` から構築します。もっとも汎用的な関数は`PyErr_SetObject()` で、 二つのオブジェクト、例外と付属値を引数にとります。これら関数に 渡すオブジェクトには`Py_INCREF()` を使う必要はありません。

例外がセットされているかどうかは、`PyErr_Occurred()` を使って非破壊的に調べられます。この関数は現在の例外オブジェクトを 返します。例外が発生していない場合には `NULL` を返します。 通常は、関数の戻り値からエラーが発生したかを判別できるはずなので、 `PyErr_Occurred()` を呼び出す必要はありません。

関数g を呼び出すf が、前者の関数の呼び出しに失敗したことを 検出すると、f 自体はエラー値 (大抵は `NULL` や `-1`) を返さねばなりません。しかし、`PyErr_*()` 関数群の いずれかを呼び出す必要は ありません -- なぜなら、g がすでに呼び出しているからです。次いでf を呼び出したコードも エラーを示す値を自らを呼び出したコード に返すことになりますが、 同様に`PyErr_*()` は呼び出しません。 以下同様に 続きます -- エラーの最も詳しい原因は、最初にエラーを検出した 関数がすでに報告しているからです。エラーが Python インタプリタの メインループに到達すると、現在実行中の Python コードは一時停止 し、 Python プログラマが指定した例外ハンドラを探し出そうとします。

(モジュールが`PyErr_*()` 関数をもう一度呼び出して、より詳細な エラーメッセージを提供するような状況があります。このような状況では そうすべきです。とはいえ、一般的な規則としては、`PyErr_*()` を何度も呼び出す必要はなく、ともすればエラーの原因に関する情報を 失う結果になりがちです: これにより、ほとんどの操作が様々な理由から 失敗するかもしれません)

ある関数呼び出しでの処理の失敗によってセットされた例外を無視するには、 `PyErr_Clear()` を呼び出して例外状態を明示的に消去 しなくてはなりません。 エラーをインタプリタには渡したくなく、自前で (何か他の作業を行ったり、 何も起こらなかったかのように見せかけるような) エラー処理を完全に 行う場合にのみ、`PyErr_Clear()` を呼び出すようにすべきです。

`malloc()` の呼び出し失敗は、常に例外にしなくては なりません -- `malloc()` (または `realloc()`) を直接呼び出しているコードは、`PyErr_NoMemory()` を 呼び出して、失敗を示す値を返さねばなりません。オブジェクトを 生成する全ての関数 (例えば `PyInt_FromLong()`) は `PyErr_NoMemory()` の呼び出しを済ませてしまうので、 この規則が関係するのは直接 `malloc()` を呼び出す コードだけです。

また、`PyArg_ParseTuple()` という重要な例外を除いて、 整数の状態コードを返す関数はたいてい、Unix のシステムコール と同じく、処理が成功した際にはゼロまたは正の値を返し、 失敗した場合には `-1` を返します。

最後に、エラー標示値を返す際に、(エラーが発生するまでに既に 生成してしまったオブジェクトに対して`Py_XDECREF()` や `Py_DECREF()` を呼び出して) ごみ処理を注意深く 行ってください!

どの例外を返すかの選択は、ユーザに完全にゆだねられます。 `PyExc_ZeroDivisionError` のように、全ての組み込みの Python 例外には対応する宣言済みの C オブジェクトがあり、直接利用できます。 もちろん、例外の選択は賢く行わねばなりません -- ファイルが開けなかったことを表すのに`PyExc_TypeError` を使ったりはしないでください (この場合はおそらく`PyExc_IOError` の方にすべきでしょう)。 引数リストに問題がある場合には、`PyArg_ParseTuple()` はたいてい `PyExc_TypeError` を送出します。 引数の値が特定の範囲を超えていたり、その他の満たすべき条件を満たさ なかった場合には、`PyExc_ValueError` が適切です。

モジュール固有の新たな例外も定義できます。定義するには、通常は ファイルの先頭部分に静的なオブジェクト変数の宣言を行います:

    static PyObject *SpamError;

そして、モジュールの初期化関数 (`initspam()`) の中で、例外オブジェクトを使って初期化します (ここでは エラーチェックを省略しています):

    PyMODINIT_FUNC
    initspam(void)
    {
        PyObject *m;

        m = Py_InitModule("spam", SpamMethods);

        SpamError = PyErr_NewException("spam.error", NULL, NULL);
        Py_INCREF(SpamError);
        PyModule_AddObject(m, "error", SpamError);
    }

Python レベルでの例外オブジェクトの名前は `spam.error` になることに注意してください。 `PyErr_NewException()` 関数は、Python ライブラリリファレンス の ``組み込み例外'' の節に述べられている`Exception` クラスを基底クラスに持つ例外クラスも作成できます (`NULL`の代わりに他のクラスを渡した場合は別です)。

`SpamError` 変数は、新たに生成された例外クラスへの参照を 維持することにも注意してください; これは意図的な仕様です! 外部のコードが例外オブジェクトをモジュールから除去できるため、 モジュールから新たに作成した例外クラスが見えなくなり、 `SpamError` がぶら下がりポインタ (dangling pointer) になってしまわないようにするために、クラスに対する参照を所有して おかねばなりません。 もし`SpamError` がぶら下がりポインタになってしまうと、 C コードが例外を送出しようとしたときにコアダンプや意図しない副作用を 引き起こすことがあります。

この例にある、関数の戻り値型に PyMODINIT_FUNC の使う 方法については後で議論します。