Python/C API の各関数における参照カウントの振る舞いは、 説明するには、参照の所有権 (ownership of references) という言葉でうまく説明できます。所有権は参照に対するもので、 オブジェクトに対するものではありません (オブジェクトは 誰にも所有されず、常に共有されています)。 ある参照の "所有" は、その参照が必要なくなった時点で Py_DECREF() を呼び出す役割を担うことを意味します。 所有権は委譲でき、あるコードが委譲によって所有権を得ると、 今度はそのコードが参照が必要なくなった際に最終的に Py_DECREF() や Py_XDECREF() を呼び出して decref する役割を担います -- あるいは、その役割を (通常はコードを 呼び出した元に) 受け渡します。 ある関数が、関数の呼び出し側に対して参照の所有権を渡すと、 呼び出し側は 新たな 参照 (new reference) を得る、と言います。 所有権が渡されない場合、呼び出し側は参照を借りる (borrow) といいます。借りた参照に対しては、何もする必要はありません。
逆に、ある関数呼び出しで、あるオブジェクトへの参照を呼び出される 関数に渡す際には、二つの可能性: 関数がオブジェクトへの参照を 盗み取る (steal) 場合と、そうでない場合があります。
参照を盗む とは、関数に参照を渡したときに、参照の所有者が その関数になったと仮定し、関数の呼び出し元には所有権がなくなるということです。
参照を盗み取る関数はほとんどありません; 例外としてよく知られているのは、
PyList_SetItem() と
PyTuple_SetItem() で、
これらはシーケンスに入れる要素に対する参照を盗み取ります (しかし、要素の
入る先のタプルやリストの参照は盗み取りません!)。これらの関数は、
リストやタプルの中に新たに作成されたオブジェクトを入れていく際の
常套的な書き方をしやすくするために、参照を盗み取るように設計されて
います; 例えば、(1, 2, "three")
というタプルを生成するコードは
以下のようになります (とりあえず例外処理のことは忘れておきます;
もっとよい書き方を後で示します):
PyObject *t; t = PyTuple_New(3); PyTuple_SetItem(t, 0, PyInt_FromLong(1L)); PyTuple_SetItem(t, 1, PyInt_FromLong(2L)); PyTuple_SetItem(t, 2, PyString_FromString("three"));
ここで、PyInt_FromLong() は新しい参照を返し、すぐに PyTuple_SetItem() に盗まれます。 参照が盗まれた後もそのオブジェクトを利用したい場合は、参照盗む関数を 呼び出す前に、Py_INCREF() を利用してもう一つの参照を取得 してください。
ちなみに、PyTuple_SetItem() はタプルに値をセットする ための 唯一の 方法です; タプルは変更不能なデータ型なので、 PySequence_SetItem() や PyObject_SetItem() を使うと上の操作は拒否されてしまいます。自分でタプルの値を入れていく つもりなら、PyTuple_SetItem() だけしか使えません。
同じく、リストに値を入れていくコードは PyList_New() と PyList_SetItem() で書けます。
しかし実際には、タプルやリストを生成して値を入れる際には、 上記のような方法はほとんど使いません。 より汎用性のある関数、Py_BuildValue() があり、 ほとんどの主要なオブジェクトをフォーマット文字列 format string の指定に基づいて C の値から生成できます。例えば、 上の二種類のコードブロックは、以下のように置き換えられます (エラーチェックにも配慮しています):
PyObject *tuple, *list; tuple = Py_BuildValue("(iis)", 1, 2, "three"); list = Py_BuildValue("[iis]", 1, 2, "three");
自作の関数に渡す引数のように、単に参照を借りるだけの要素に 対しては、PyObject_SetItem() とその仲間を 使うのがはるかに一般的です。 その場合、参照カウントをインクリメントする必要がなく、 参照を引き渡せる (``参照を盗み取らせられる'') ので、 参照カウントに関する動作はより健全になります。 例えば、以下の関数は与えられた要素をリスト中の全ての要素の値に セットします:
int set_all(PyObject *target, PyObject *item) { int i, n; n = PyObject_Length(target); if (n < 0) return -1; for (i = 0; i < n; i++) { PyObject *index = PyInt_FromLong(i); if (!index) return -1; if (PyObject_SetItem(target, index, item) < 0) return -1; Py_DECREF(index); } return 0; }
関数の戻り値の場合には、状況は少し異なります。 ほとんどの関数については、参照を渡してもその参照に対する 所有権が変わることがない一方で、あるオブジェクトに対する参照を 返すような多くの関数は、参照に対する所有権を呼び出し側に与えます。 理由は簡単です: 多くの場合、関数が返すオブジェクトはその場で (on the fly) 生成されるため、呼び出し側が得る参照は生成された オブジェクトに対する唯一の参照になるからです。 従って、PyObject_GetItem() や PySequence_GetItem() のように、オブジェクトに対する 参照を返す汎用の関数は、常に新たな参照を返します (呼び出し側 が参照の所有者になります)。
重要なのは、関数が返す参照の所有権を持てるかどうかは、どの関数を 呼び出すかだけによる、と理解することです -- 関数呼び出し時の お飾り (関数に引数として渡したオブジェクトの型) は この問題には関係ありません! 従って、PyList_GetItem() を使ってリスト内の要素を 得た場合には、参照の所有者にはなりません -- が、同じ要素を 同じリストから PySequence_GetItem() (図らずもこの関数は 全く同じ引数をとります) を使って取り出すと、返されたオブジェクト に対する参照を得ます。
以下は、整数からなるリストに対して各要素の合計を計算する関数を どのようにして書けるかを示した例です; 一つは PyList_GetItem() を使っていて、 もう一つは PySequence_GetItem() を使っています。
long sum_list(PyObject *list) { int i, n; long total = 0; PyObject *item; n = PyList_Size(list); if (n < 0) return -1; /* リストではない */ for (i = 0; i < n; i++) { item = PyList_GetItem(list, i); /* 失敗しないはず */ if (!PyInt_Check(item)) continue; /* 整数でなければ読み飛ばす */ total += PyInt_AsLong(item); } return total; }
long sum_sequence(PyObject *sequence) { int i, n; long total = 0; PyObject *item; n = PySequence_Length(sequence); if (n < 0) return -1; /* 長さの概念がない */ for (i = 0; i < n; i++) { item = PySequence_GetItem(sequence, i); if (item == NULL) return -1; /* シーケンスでないか、その他の失敗 */ if (PyInt_Check(item)) total += PyInt_AsLong(item); Py_DECREF(item); /* GetItem で得た所有権を放棄する */ } return total; }
ご意見やご指摘をお寄せになりたい方は、 このドキュメントについて... をご覧ください。