別のオブジェクトの属性としてタプルを作成し、タプルに項目を設定する Python 拡張モジュールがあります。このモジュールを Python で実行すると、常にエラーが発生します。SystemError: bad argument to internal function
のドキュメントを読みPyTuple
、プログラムを数時間デバッグした後でも、一体何が起こっているのか理解できませんでした。デバッガーを介してプログラムを実行すると、Python インタープリター内のライブラリー呼び出し内で問題が発生していることが示されました。というわけで、ようやくPythonのソースコードを見て、ようやく問題に気づきました。関数には、PyTuple_SetItem
私が知らなかった興味深い制限があり、明示的に文書化されていません。
Python ソースの重要な関数を次に示します (わかりやすくするために編集しています)。
int PyTuple_SetItem(register PyObject *op, register Py_ssize_t i, PyObject *newitem)
{
.....
if (!PyTuple_Check(op) || op->ob_refcnt != 1) {
Py_XDECREF(newitem);
PyErr_BadInternalCall();
return -1;
}
.....
}
ここで重要な行は条件op->ob_refcnt != 1です。ここに問題があります: Tuple の ref-count が 1 でない限り、呼び出すことさえできませPyTuple_SetItem
ん。結局のところ、タプルは不変であるはずなので、これは理にかなっていると思います。したがって、この制限は、C コードを Python 型システムの抽象化とより一致させるのに役立ちます。 PyTuple_SetItem
PyTuple_New()
ただし、この制限はどこにも文書化されていません。関連するドキュメントはhereとhereにあるようですが、どちらもこの制限を指定していません。ドキュメントでは基本的に、 を呼び出すとPyTuple_New(X)
、タプル内のすべての項目が に初期化されると書かれていますNULL
。は有効な Python 値ではないためNULL
、Tuple をインタープリターに返す前に、Tuple 内のすべてのスロットに適切な Python 値が入力されていることを確認するのは、拡張モジュール プログラマの責任です。しかし、Tuple オブジェクトの参照カウントが 1 のときにこれを行う必要があるとはどこにも述べていません。
さて、問題は、この (文書化されていない?) 制限を認識していなかったため、基本的に自分自身をコーナーにコーディングしたことですPyTuple_SetItem
。私のコードは、タプル自体が別のオブジェクトの属性になるまで項目をタプルに挿入するのが非常に不便なように構造化されています。そのため、タプルにアイテムを入力するときが来ると、タプルはすでにより高い参照カウントを持っています。
おそらくコードを再構築する必要がありますが、Tuple の参照カウントを一時的に 1 に設定し、アイテムを挿入してから、元の参照カウントを復元することを真剣に検討していました。もちろん、これは恐ろしいハックであり、恒久的な解決策ではありません。とにかく、タプルの参照カウントに関する要件がどこかに文書化されているかどうかを知りたいです。それは単なる CPython の実装の詳細ですか、それとも API ユーザーが期待される動作として信頼できるものですか?