## 5 抽象的なプロトコルのサポート

Python はいくつもの 抽象的な “プロトコル” をサポートしています。 これらを使用する特定のインターフェイスについては Python/C API Reference Manual の 「Abstract Objects Layer」の章で解説されて います。

これら多数の抽象的なインターフェイスは、Python の実装が開発される 初期の段階で定義されていました。とりわけ数値や辞書、そしてシーケンスなどの プロトコルは最初から Python の一部だったのです。それ以外のプロトコルは その後追加されました。 型の実装にあるいくつかのハンドラルーチンに依存するようなプロトコル のために、古いプロトコルはハンドラの入ったオプションのブロックとして 定義し、型オブジェクトから参照するようになりました。 タイプオブジェクトの主部に追加のスロットをもつ新しいプロトコルについては、 フラグ用のビットを立てることでそれらのスロットが存在しており、 インタプリタがチェックすべきであることを指示できます。 (このフラグ用のビットは、そのスロットの値が 非 `NULL`であることを 示しているわけではありません。フラグはスロットの存在を示すのに使えますが、 そのスロットはまだ埋まっていないかもしれないのです。)

        PyNumberMethods   tp_as_number;
        PySequenceMethods tp_as_sequence;
        PyMappingMethods  tp_as_mapping;

お使いのオブジェクトを数値やシーケンス、あるいは辞書のようにふるまうように したいならば、それぞれに C の `PyNumberMethods` 構造体、 `PySequenceMethods` 構造体、または `PyMappingMethods` 構造体のアドレスを 入れます。これらに適切な値を入れても入れなくてもかまいません。 これらを使った例は Python の配布ソースにある Objects で みつけることができるでしょう。

        hashfunc tp_hash;

この関数は、もし使うのならば、これはお使いの型のインスタンスの ハッシュ番号を返すようにします。以下はやや的はずれな例ですが :

    static long
    newdatatype_hash(newdatatypeobject *obj)
    {
        long result;
        result = obj->obj_UnderlyingDatatypePtr->size;
        result = result * 3;
        return result;
    }

        ternaryfunc tp_call;

この関数は、その型のインスタンスが「関数として呼び出される」ときに呼ばれます。 たとえばもし `obj1` にそのインスタンスが入っていて、Python スクリプトで `obj1('hello')` を実行したとすると、 `tp_call` ハンドラが 呼ばれます。

この関数は 3つの引数をとります:

1. arg1 にはその呼び出しの対象となる、 そのデータ型のインスタンスが入ります。たとえば呼び出しが `obj1('hello')` の場合、arg1 は `obj1` になります。

2. arg2 は呼び出しの引数を格納しているタプルです。 ここから引数を取り出すには `PyArg_ParseTuple()` を使います。

3. arg3 はキーワード引数のための辞書です。これが `NULL` 以外で キーワード引数をサポートしているなら、`PyArg_ParseTupleAndKeywords()` をつかって引数を取り出せます。キーワード引数をサポートしていないのに これが `NULL` 以外の場合は、キーワード引数はサポートしていない旨の メッセージとともに `TypeError` を発生させてください。

以下はこの call 関数をてきとうに使った例です。

    /* call 関数の実装。
     *    obj1 : 呼び出しを受けるインスタンス。
     *    obj2 : 呼び出しのさいの引数を格納するタプル、この場合は 3つの文字列。
     */
    static PyObject *
    newdatatype_call(newdatatypeobject *obj, PyObject *args, PyObject *other)
    {
        PyObject *result;
        char *arg1;
        char *arg2;
        char *arg3;

        if (!PyArg_ParseTuple(args, "sss:call", &arg1, &arg2, &arg3)) {
            return NULL;
        }
        result = PyString_FromFormat(
            "Returning -- value: [\%d] arg1: [\%s] arg2: [\%s] arg3: [\%s]\n",
            obj->obj_UnderlyingDatatypePtr->size,
            arg1, arg2, arg3);
        printf("\%s", PyString_AS_STRING(result));
        return result;
    }

        /* バージョン 2.2 以降で追加 */
        /* Iterators */
        getiterfunc tp_iter;
        iternextfunc tp_iternext;

これらの関数はイテレータ用プロトコルをサポートします。 オブジェクトが、その (ループ中に順に生成されていくかもしれない) 内容を 巡回 (訳注: イテレータでひとつずつ要素をたどっていくこと) する イテレータをサポートしたい場合は、`tp_iter` ハンドラを 実装する必要があります。 `tp_iter` ハンドラによって返される オブジェクトは `tp_iter` と `tp_iternext` の両方を実装する必要が あります。 どちらのハンドラも、それが呼ばれたインスタンスをひとつだけ引数としてとり、 新しい参照を返します。エラーが起きた場合には例外を設定してから `NULL`を返す必要があります。

巡回可能な要素を表現するオブジェクトに対しては、`tp_iter` ハンドラが イテレータオブジェクトを返す必要があります。イテレータオブジェクトは 巡回中の状態を保持する責任をもっています。お互いに干渉しない複数の イテレータの存在を許すようなオブジェクト (リストやタプルがそうです) の場合は、 新しいイテレータを作成して返す必要があります。 (巡回の結果生じる副作用のために) 一回だけしか巡回できないオブジェクトの場合は、それ自身への参照を返すような ハンドラと、 `tp_iternext` ハンドラも実装する必要があります。 ファイルオブジェクトはそのようなイテレータの例です。

イテレータオブジェクトは両方のハンドラを実装する必要があります。 `tp_iter` ハンドラはそのイテレータへの新しい参照を返します (これは破壊的にしか巡回できないオブジェクトに対する `tp_iter` ハンドラと同じです)。 `tp_iternext` ハンドラはその次のオブジェクトがある場合、それへの 新しい参照を返します。巡回が終端に達したときは例外を出さずに `NULL` を 返してもいいですし、 `StopIteration` を放出してもかまいません。 例外を使わないほうがやや速度が上がるかもしれません。 実際のエラーが起こったときには、例外を放出して `NULL`を返す必要があります。