我正在努力理解一些围绕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);
}
我是否假设CPython将把它的类型转换回一个3-param函数,其中第一个参数是args(与处理程序的_self_and_name_tuple第一个参数匹配),对吗?
而且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副本,并且第一个从未被触及,因为它是基类
如果确实发生这种情况,即如果此类确实通过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){...}
现在,他们必须通过调用以下三个函数之一来将该方法存储在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提取了处理程序函数,这是三个处理程序之一。但是,由于MethodDefExt的构造函数,它们都被类型转换为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 ) { }
即第三。我读过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只接受两个参数!!!
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函数指针*第二个参数必须是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)
我会冒险回答:
PyObject* PyCFunction_New(PyMethodDef* ml, PyObject* data)
PyCFunction_New可能会创建一个Callable-Type PyObject,并以一个函数(包装在ml中)和附加数据(包装在self中)为底
第二个参数可以是任何东西,实际上它甚至不需要是PyObject *。当Python执行ml内部打包的函数时,这将是第一个参数。后续参数取决于ml-> ml_flags,如下所述。
第一个参数是PyMethodDef对象,我们可以使用它封装函数。
struct PyMethodDef {
const char *ml_name; /* The name of the built-in function/method */
PyCFunction ml_meth; /* The C function that implements it */
int ml_flags; /* Combination of METH_xxx flags, which mostly
describe the args expected by the C func */
const char *ml_doc; /* The __doc__ attribute, or NULL */
};
typedef struct PyMethodDef PyMethodDef;
因此,它包含一个(特定的)函数指针:
typedef PyObject *(*PyCFunction)(PyObject*, PyObject*);
...和一个标志,
/* Flag passed to newmethodobject */
/* #define METH_OLDARGS 0x0000 -- unsupported now */
#define METH_VARARGS 0x0001
#define METH_KEYWORDS 0x0002
/* METH_NOARGS and METH_O must not be combined with the flags above. */
#define METH_NOARGS 0x0004
#define METH_O 0x0008
https://docs.python.org/3.4/c-api/structures.html
我们可以通过这种方式将3种函数传递给Python:
PyObject*foo( PyObject* data ) // ml_meth=METH_NOARGS
PyObject*foo( PyObject* data, PyObject* args ) // ml_meth=METH_VARARGS
PyObject*foo( PyObject* data, PyObject* args, PyObject* kwds ) // ml_meth=METH_KEYWORDS
编辑:https://docs.python.org/3/tutorial/classes.html#method-objects
如果您仍然不了解方法的工作方式,那么看一下实现也许可以澄清问题。当引用的实例属性不是数据属性时,将搜索其类。如果名称表示作为函数对象的有效类属性,则通过将实例对象和刚在抽象对象中一起找到的函数对象打包(指向)来创建方法对象:这是方法对象。当使用实参列表调用方法对象时,将从实例对象和实参列表构造一个新的实参列表,并使用该新的实参列表来调用函数对象。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句