PyCFunction_New / PyCFunction_NewEx的文档

i

我正在努力理解一些围绕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)
i

我会冒险回答:

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] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

PyCFunction_New / PyCFunction_NewEx的文档

来自分类Dev

我的 PyCFunction 第一次工作,但在连续迭代后导致段错误

来自分类Dev

更改文档参数后,NSCocoaErrorDomain 256 开启 *new* 文档

来自分类Dev

为什么Pickle不会像文档中所说的那样调用__new__?

来自分类Dev

微软关于 IServiceCollection.Add(new ServiceDescriptor(...)) 的有用文档在哪里?

来自分类Dev

MongoDB:在$ look阶段之后将New Field添加到现有子文档中,或将查找响应合并到主文档中

来自分类Dev

这个JavaScript文档的硒Python客户端等效项是什么。getElementById('#searchboxinput')。dispatchEvent(new MouseEvent('keyup'));

来自分类Dev

Powershell“文档”或“我的文档”

来自分类Dev

TIdHTTPWebBrokerBridge文档

来自分类Dev

MiniUPnP文档

来自分类Dev

CodeAuthzpComputeImageHash的文档

来自分类Dev

uinput的文档

来自分类Dev

MiniUPnP文档

来自分类Dev

/ sys /文档?

来自分类Dev

/ sys /文档?

来自分类Dev

JavaBean文档

来自分类Dev

Ubuntu 文档

来自分类Dev

ThreadWaitReason 的文档

来自分类Dev

ENMLWriter 的文档

来自分类Dev

Google文档文档私人评论

来自分类Dev

文档写入文档get元素

来自分类Dev

如何根据此文档的子文档ID获取文档

来自分类Dev

Mongodb:从文档中拉出子文档

来自分类Dev

Couchbase-大文档还是小文档?

来自分类Dev

MongoDB提取子文档作为实际文档

来自分类Dev

按子文档拆分文档

来自分类Dev

在返回文档的子文档中查找

来自分类Dev

猫鼬更新子文档的子文档

来自分类Dev

根据子文档值搜索mongodb文档