PyCFunction_New を中心に展開するPyCXXコード (C++ Python ラッパー)を理解するのに苦労しています。
誰かがこの機能がどのように機能するか説明できますか?
(CPythonソース コードからはわかりません。)
ここでは、私が抱えている問題について詳しく説明します。これはおそらくそれほど一般的な用途ではないため、上記の行に罫線を付けました。
尋ねる理由は、私が奇妙なコードを扱っているからです。私はキーワードメソッドハンドラ関数を持っています:
static PyObject* keyword_handler( PyObject* _self_and_name_tuple,
PyObject* _args,
PyObject* _keywords ) { }
次のように保存されています。
PyMethodDef meth_def_ext;
meth_def_ext.ml_meth = reinterpret_cast<PyCFunction>( _handler );
meth_def.ml_flags = METH_VARARGS | METH_KEYWORDS;
次に、PyCFunction_New にバンドルされます。
MethodDefExt<T>* method_def_ext = ...;
Tuple args{2}; // Tuple wraps a CPython Tuple
args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };
PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() );
return Object(func, true);
}
最初のパラメーターが args (ハンドラーの _self_and_name_tuple の最初のパラメーターと一致する) である 3 パラメーター関数に、CPython が型キャストを処理すると仮定するのは正しいでしょうか?
そして、CPython は、「myFunc(7, a=1)」を解析しなければならないという事実から、実際には 3-param 関数とも呼ばれるキーワードを実際に扱っていることを知るだけでしょうか?
これは正しくないようです。
たぶん、CPython は args 1を PyMethodDef に型キャストし、.ml_flags を検査しています。
それが起こっている場合、私が作業しているコードには次のようなものがあるため、知る必要があります。
template<class T>
class MethodDefExt //: public PyMethodDef <-- I commented this out
{
// ... Constructors ...
PyMethodDef meth_def;
method_noargs_function_t ext_noargs_function = nullptr;
method_varargs_function_t ext_varargs_function = nullptr;
method_keyword_function_t ext_keyword_function = nullptr;
Object py_method;
};
元の形式では、PyMethodDef の 2 つのコピーがあったに違いないと思います。
これが実際に起こっている場合、つまり、このクラスが実際に PyCFunction_New の内部によって PyMethodDef に型キャストされている場合、これは危険です。
確かに、誰かが MethodDefExt の前にメンバー変数を追加すると、型キャストが壊れます。これは薄っぺらい…
私が扱っているクラスは、将来の C++ コーダーがカスタム Python タイプを実装し、このタイプ内で Python から呼び出すことができるメソッドを実装できるようにします。
そこで彼らはMyExt : CustomExtを派生させ、メソッドを書きます:
// one of these three
MyExt::foo(){...}
MyExt::foo(PyObject* args){...}
MyExt::foo(PyObject* args, PyObject* kw){...}
次に、これら 3 つの関数の適切な 1 つを呼び出して、このメソッドをlookupに格納する必要があります。
typedef Object (T::*method_noargs_function_t)();
static void add_noargs_method( const char* name,
method_noargs_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,noargs_handler,doc};
}
typedef Object (T::*method_varargs_function_t)( const Tuple& args );
static void add_varargs_method( const char* name,
method_varargs_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,varargs_handler,doc};
}
typedef Object (T::*method_keyword_function_t)( const Tuple& args, const Dict& kws );
static void add_keyword_method( const char* name,
method_keyword_function_t function ) {
lookup()[std::string{name}] = new MethodDefExt<T>
{name,function,keyword_handler,doc};
}
それぞれに関連付けられたハンドラ関数があることに注意してください。これらのハンドラ関数は、CustomExtの静的メソッドです。静的メソッドへのポインタは CPython から呼び出すことができるためです。つまり、これは単なる標準 C スタイルの関数ポインタです。
したがって、Python がこのfoo関数のポインターを必要とする場合、ここでインターセプトします。
// turn a name into function object
virtual Object getattr_methods( const char* _name )
{
std::string name{ _name };
// see if name exists and get entry with method
auto i = lookup().find( name );
DBG_LINE( "packaging relevant C++ method and extension object instance into PyCFunction" );
// assume name was found in the method map
MethodDefExt<T>* method_def_ext = i->second;
// this must be the _self_and_name_tuple that gets received
// as the first parameter by the handler
Tuple args{2};
args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };
このメソッドのハンドラーを呼び出す Python 関数を作成します (このオブジェクト args[0] にメソッド自体の詳細 args 1を渡します)。ハンドラーは、エラーをトラップしながらメソッドを実行します。
この時点ではハンドラーを実行しないことに注意してください。代わりに、この Python 関数を Python ランタイムに戻します。Python コーダーは、関数を実行することを望まず、関数へのポインターを取得したかっただけかもしれません: fp = MyExt.func;
PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() );
X (下記参照) & method_def_ext->meth_def は、3 つのハンドラーの 1 つであるハンドラー関数を引き出します。ただし、MethodDefExt s コンストラクターのおかげで、それらはすべて PyCFunction オブジェクトに型キャストされています。これは、キーワード ハンドラーのパラメーター リストが間違っていることを意味します。
return Object(func, true);
}
(SOのフォーマッタがコメントをコードコメントとして処理していなかったため、コメントを分割する必要がありました)
私が苦労しているのはこれです: fooがキーワードを取る関数であるとしましょう。したがって、その署名は次のようになります。
MyExt::foo(PyObject* args, PyObject* kw)
一致するハンドラーは次のようになります。
static PyObject* noargs_handler( PyObject* _self_and_name_tuple,
PyObject* ) { }
static PyObject* varargs_handler( PyObject* _self_and_name_tuple,
PyObject* _args ) { }
static PyObject* keyword_handler( PyObject* _self_and_name_tuple,
PyObject* _args,
PyObject* _keywords ) { }
つまり、3番目のもの。Python が追加の最初の_self_and_name_tupleパラメータを提供していることを読みました。
foo をルックアップに登録するときは、次のハンドラーを提供します。
typedef Object (T::*method_keyword_function_t)( const Tuple& args, const Dict& kws );
static void add_keyword_method( const char* name, method_keyword_function_t function ) {
methods()[std::string{name}] = new MethodDefExt<T> {name,function,keyword_handler,doc};
}
MethodDefExtの特定のコンストラクターを見ると、
// VARARGS + KEYWORD
MethodDefExt (
const char* _name,
method_keyword_function_t _function,
method_keyword_call_handler_t _handler
)
{
meth_def.ml_name = const_cast<char *>( _name );
meth_def.ml_doc = nullptr;
meth_def.ml_meth = reinterpret_cast<PyCFunction>( _handler );
meth_def.ml_flags = METH_VARARGS | METH_KEYWORDS;
ext_noargs_function = nullptr;
ext_varargs_function = nullptr;
ext_keyword_function = _function;
}
... このハンドラーをPyCFunctionに型キャストすることがわかります
しかし、PyCFunctionは 2 つの引数しか取りません!!!
typedef PyObject *(*PyCFunction)(PyObject *, PyObject *);
これにハンドラーを型キャストしています。そして、これらのハンドラーには 2 つまたは 3 つのパラメーターがあります。
これは本当に間違っているように見えます。
そして、上記のように、CPython が foo を実行したい場合、このmeth_def.ml_methを取得してPyCFunction_Newにフィードします。
Tuple args{2};
args[0] = Object{ this };
args[1] = Object{ PyCapsule_New( (void*)method_def_ext, nullptr, nullptr ), true };
PyObject* func = PyCFunction_New( & method_def_ext->meth_def, args.ptr() ); // https://github.com/python/cpython/blob/master/Objects/methodobject.c#L19-L48
だから私は推測することができます: * PyCFunction_New の最初のパラメーターは PyCFunction 関数ポインターでなければなりません * 2 番目のパラメーターは PyObject でなければなりません* _self_and_name_tuple
そして、これを CPython にフィードバックしています。私の推測では、CPython が 'foo(7, a=1,b=2)' を使用したい場合、7 を args にパッケージ化し、a=1,b=2 を kwds にパッケージ化して呼び出します。 :
[the PyCFunction function pointer](_self_and_name_tuple, args, kwds)