Documentation for PyCFunction_New / PyCFunction_NewEx

Documentation for PyCFunction_New / PyCFunction_NewEx

本文关键字:PyCFunction NewEx New Documentation for      更新时间:2023-10-16

我正在努力理解一些围绕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 参数函数是否正确,其中第一个参数是 args(与处理程序_self_and_name_tuple第一个参数匹配(?

CPython 只能从它必须解析的事实中知道:"myFunc(7, a=1("它实际上是在处理一个关键字,又名 3 参数函数?

这看起来不对。

也许CPython正在将args1类型转换回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){...}

现在,他们必须通过调用以下三个函数中的相应一个来将此方法存储在查找中:

    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] 中传递方法本身 args1 的详细信息时(。处理程序将负责在捕获错误时运行该方法。

请注意,此时我们不执行处理程序相反,我们将这个 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可能会创建一个可调用类型的 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

如果您仍然不了解方法的工作原理,请查看 执行也许可以澄清问题。当实例属性 被引用,不是数据属性,搜索其类。如果 该名称表示作为函数对象的有效类属性,即 方法对象是通过打包(指向(实例对象来创建的 以及刚刚在一个抽象对象中找到的函数对象: 这是方法对象。当使用方法 参数列表,从实例构造新的参数列表 对象和参数列表,函数对象被调用 这个新的参数列表。

我也有同样的问题寻找这两个函数的文档。谷歌搜索我找到了这个链接:https://github.com/python/cpython/blob/main/Objects/methodobject.c

看到CPython代码,我发现更容易理解这个主题。

PyObject *
PyCFunction_New(PyMethodDef *ml, PyObject *self)
{
    return PyCFunction_NewEx(ml, self, NULL);
}
PyObject *
PyCFunction_NewEx(PyMethodDef *ml, PyObject *self, PyObject *module)
{
    return PyCMethod_New(ml, self, module, NULL);
}
PyObject *
PyCMethod_New(PyMethodDef *ml, PyObject *self, PyObject *module, PyTypeObject *cls)
{
    /* Figure out correct vectorcall function to use */
    vectorcallfunc vectorcall;
    switch (ml->ml_flags & (METH_VARARGS | METH_FASTCALL | METH_NOARGS |
                            METH_O | METH_KEYWORDS | METH_METHOD))
    {
        case METH_VARARGS:
        case METH_VARARGS | METH_KEYWORDS:
            /* For METH_VARARGS functions, it's more efficient to use tp_call
             * instead of vectorcall. */
            vectorcall = NULL;
            break;
        case METH_FASTCALL:
            vectorcall = cfunction_vectorcall_FASTCALL;
            break;
        case METH_FASTCALL | METH_KEYWORDS:
            vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS;
            break;
        case METH_NOARGS:
            vectorcall = cfunction_vectorcall_NOARGS;
            break;
        case METH_O:
            vectorcall = cfunction_vectorcall_O;
            break;
        case METH_METHOD | METH_FASTCALL | METH_KEYWORDS:
            vectorcall = cfunction_vectorcall_FASTCALL_KEYWORDS_METHOD;
            break;
        default:
            PyErr_Format(PyExc_SystemError,
                         "%s() method: bad call flags", ml->ml_name);
            return NULL;
    }

PyCFunction_New和PyCFunction_NewEx只是PyCMethod_New的特例。