web-dev-qa-db-ja.com

Python C-APIで派生型を動的に作成する方法

Python用のC拡張モジュールの作成に関するチュートリアル で定義されているタイプNoddyがあると仮定します。ここで、派生型を作成し、Noddy__new__()メソッドのみを上書きします。

現在、私は次のアプローチを使用しています(読みやすさのためにエラーチェックを取り除いています):

_PyTypeObject *BrownNoddyType =
    (PyTypeObject *)PyType_Type.tp_alloc(&PyType_Type, 0);
BrownNoddyType->tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE;
BrownNoddyType->tp_name = "noddy.BrownNoddy";
BrownNoddyType->tp_doc = "BrownNoddy objects";
BrownNoddyType->tp_base = &NoddyType;
BrownNoddyType->tp_new = BrownNoddy_new;
PyType_Ready(BrownNoddyType);
_

これは機能しますが、それが正しい方法かどうかはわかりません。ヒープに型オブジェクトを動的に割り当てるため、 _Py_TPFLAGS_HEAPTYPE_ フラグも設定する必要があると予想していましたが、そうするとインタープリターでセグメンテーション違反が発生します。

また、type()などを使用してPyObject_Call()を明示的に呼び出すことも考えましたが、そのアイデアを破棄しました。関数BrownNoddy_new()をPython関数オブジェクトでラップし、___new___をこの関数オブジェクトにマッピングする辞書を作成する必要があります。これはばかげているようです。

これについて行くための最良の方法は何ですか?私のアプローチは正しいですか?見逃したインターフェース機能はありますか?

更新

Python-devメーリングリストの関連トピックには2つのスレッドがあります (1)(2) 。これらのスレッドといくつかの実験から、タイプがtype()の呼び出しによって割り当てられない限り、_Py_TPFLAGS_HEAPTYPE_を設定すべきではないと推測します。これらのスレッドには、タイプを手動で割り当てるか、type()を呼び出す方がよいかどうかにかかわらずさまざまな推奨事項があります。 _tp_new_スロットに入るはずのC関数をラップするための推奨される方法が何であるかを知っていれば、後者に満足するでしょう。通常のメソッドの場合、この手順は簡単です。 PyDescr_NewMethod() を使用して、適切なラッパーオブジェクトを取得できます。 __new__()メソッドのそのようなラッパーオブジェクトを作成する方法はわかりませんが、そのようなラッパーオブジェクトを作成するには、文書化されていない関数PyCFunction_New()が必要な場合があります。

73
Sven Marnach

Python 3と互換性があるように拡張機能を変更しているときに同じ問題が発生し、それを解決しようとしたときにこのページが見つかりました。

最終的には、Pythonインタープリター、 PEP 0384 )のソースコードと C-API のドキュメントを読んで解決しました。

Py_TPFLAGS_HEAPTYPEフラグを設定すると、インタプリタはPyTypeObjectPyHeapTypeObjectとして再キャストするように指示されます。これには、割り当てが必要な追加のメンバーが含まれています。ある時点で、インタープリターはこれらの追加メンバーを参照しようとしますが、それらを未割り当てのままにすると、セグメンテーション違反が発生します。

Python 3.2では、動的型の作成を簡素化するC構造体PyType_SlotPyType_SpecおよびC関数PyType_FromSpecが導入されました。簡単に言うと、PyType_SlotPyType_Specを使用してPyTypeObjecttp_*メンバーを指定し、PyType_FromSpecを呼び出して割り当てとメモリを初期化します。

PEP 0384から、次のようになります。

typedef struct{
  int slot;    /* slot id, see below */
  void *pfunc; /* function pointer */
} PyType_Slot;

typedef struct{
  const char* name;
  int basicsize;
  int itemsize;
  int flags;
  PyType_Slot *slots; /* terminated by slot==0. */
} PyType_Spec;

PyObject* PyType_FromSpec(PyType_Spec*);

(上記はPEP 0384からの文字通りのコピーではなく、const char *docのメンバーとしてPyType_Specも含まれています。ただし、そのメンバーはソースコードに表示されません。)

元の例でこれらを使用するために、基本クラスBrownNoddyのC構造体を拡張するC構造体Noddyがあると仮定します。次に、次のようになります。

PyType_Slot slots[] = {
    { Py_tp_doc, "BrownNoddy objects" },
    { Py_tp_base, &NoddyType },
    { Py_tp_new, BrownNoddy_new },
    { 0 },
};
PyType_Spec spec = { "noddy.BrownNoddy", sizeof(BrownNoddy), 0,
                      Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, slots };
PyTypeObject *BrownNoddyType = (PyTypeObject *)PyType_FromSpec(&spec);

これにより、PyType_Readyの呼び出しに加えて、Py_TPFLAGS_HEAPTYPEの設定、PyHeapTypeObjectの追加メモリの割り当てと初期化など、動的タイプの作成に必要なすべてのことを元のコードで実行する必要があります。 。

お役に立てば幸いです。

4
Andy Wood

この答えがひどい場合は前もってお詫びしますが、このアイデアの実装は PythonQt にあります。特に、次のファイルが参考になると思います。

PythonQtClassWrapper_initからのこのフラグメントは、やや興味深いものとして私に飛び出します。

static int PythonQtClassWrapper_init(PythonQtClassWrapper* self, PyObject* args, PyObject* kwds)
{
  // call the default type init
  if (PyType_Type.tp_init((PyObject *)self, args, kwds) < 0) {
    return -1;
  }

  // if we have no CPP class information, try our base class
  if (!self->classInfo()) {
    PyTypeObject*  superType = ((PyTypeObject *)self)->tp_base;

    if (!superType || (superType->ob_type != &PythonQtClassWrapper_Type)) {
      PyErr_Format(PyExc_TypeError, "type %s is not derived from PythonQtClassWrapper", ((PyTypeObject*)self)->tp_name);
      return -1;
    }

    // take the class info from the superType
    self->_classInfo = ((PythonQtClassWrapper*)superType)->classInfo();
  }

  return 0;
}

PythonQtはラッパージェネレーターを使用しているため、要求しているものと完全には一致していませんが、個人的には、vtableをアウトスマートにしようとするのは最適な設計ではないと思います。基本的に、PythonにはさまざまなC++ラッパージェネレーターがあり、人々は正当な理由でそれらを使用しています-それらは文書化されており、検索結果やスタックオーバーフローに浮かんでいる例があります。これまで誰も見たことのないソリューションをロールバックすると、問題が発生した場合にデバッグするのがはるかに難しくなります。クローズドソースであっても、それを維持しなければならない次の人は頭をかいてあなたを傷つけます。一緒に来るすべての新しい人にそれを説明する必要があります。

コードジェネレーターが機能するようになったら、基になるC++コードを維持するだけで、拡張コードを手動で更新または変更する必要はありません。 (これはおそらくあなたが行った魅力的な解決策からそれほど遠くはありません)

提案された解決策は、新しく導入された PyCapsule が提供する型安全性を破る例です もう少し保護 (指示どおりに使用された場合)。

したがって、派生/サブクラスをこのように実装することは長期的には最善の選択ではないかもしれませんが、コードをラップしてvtableに最善を尽くさせ、新しい人に質問がある場合は、 whateversolutionfitsbest のドキュメント。

これは私の意見です。 :D

2

これを行う方法を理解するための1つの方法は、SWIGを使用してバージョンを作成することです。それが何を生成するかを見て、それが一致するか、または別の方法で行われるかどうかを確認します。 SWIGを書いている人々は、Pythonの拡張について深く理解していることがわかります。とにかく彼らがどのように物事を行うかを見るために傷つけることはできません。この問題を理解するのに役立つ場合があります。

1
Demolishun