Python C扩展中的动态方法切换

Python C扩展中的动态方法切换,python,c,class,methods,Python,C,Class,Methods,我想创建一个PythonC扩展模块,其中包含一个类,该类可以根据\uuu init\uuu()方法参数值动态地别名/更改/选择其方法。我在“扩展和嵌入Python解释器”文档中找不到解决方案。为了说明这一点,下面是一个工作的Python示例,我想创建等效的Python C扩展类: class Test: def __init__(self, var): if var == 'x': self.func = self.func_x e

我想创建一个PythonC扩展模块,其中包含一个类,该类可以根据
\uuu init\uuu()
方法参数值动态地别名/更改/选择其方法。我在“扩展和嵌入Python解释器”文档中找不到解决方案。为了说明这一点,下面是一个工作的Python示例,我想创建等效的Python C扩展类:

class Test:
    def __init__(self, var):
        if var == 'x':
            self.func = self.func_x
        else:
            self.func = self.func_y

    def func_x(self):
        print('func_x')

    def func_y(self): 
        print('func_y')

list = [Test('x'), Test('y'), Test('z')]
for i in range(len(list)):
    list[i].func()
我想编写一个相当于
Test
类的C代码,但创建
list
对象,使用Python C扩展名
Test
元素,使用别名
func()
方法,使用Python

作为一个示例实现,以位于的
noddy2
模块的
Noddy
类的Python文档示例为例,如何扩展此示例Python C扩展代码以允许在
Noddy_init()
函数中切换此动态方法?可以复制和修改
Noddy_name()
函数,然后修改
Noddy_init()
以基于
第一个
参数值将
self.func
设置为一个或另一个。您将
self.func()
定义为
PyMemberDef
还是
PyObject
?是否应通过tp_方法或tp_成员在
PyTypeObject
中注册?在定义了
self.func()
之后,所需的C函数如何动态别名到它


干杯

您可以通过常规的
PyMethodDef
在C中实现方法
func
,并从
func
调用
func\u a
func\u b
,具体取决于构造函数中存储的标志。

我发现了一种不同的解决方法或破解方法,可以解决速度问题。但是,这不是一个理想的或真正的python解决方案,因为它不会在通过
\uuu init\uuu()
方法参数实例化类时别名类方法。它也不理想,因为
func()=NULL
方法定义可能会导致问题。这个技巧是有一个自定义的
\uuu getattr\uuu()
方法,它为
func()
返回所需的方法

下面是实现原始问题的
Test
Python类的C代码(它与Python 2和3兼容)。由于在示例中创建
列表
变量时调用了
\uuuu getattr\uuuu()
方法,因此每个列表元素只调用一次。因此,当以这种方式使用时,对
func()
的多次调用,切换逻辑只发生一次,从而解决了速度问题

#include <Python.h>
#include "structmember.h"
#include <stdio.h>

/* Declaration of the Test class and its contents. */
typedef struct {
    PyObject_HEAD
    PyObject *var;    /* The self.var string. */
} Test;


/* Class method func_x. */
static PyObject * func_x(Test *self, PyObject *args) {
    printf("func_x\n");
    Py_INCREF(Py_None);
    return Py_None;
}


/* Class method func_y. */
static PyObject * func_y(Test *self, PyObject *args) {
    printf("func_y\n");
    Py_INCREF(Py_None);
    return Py_None;
}


/* Definition of all functions of the test module. */
static PyMethodDef test_methods[] = {
    {NULL, NULL, 0, NULL}        /* Sentinel. */
};


/* Definition of all methods of the Test class. */
static PyMethodDef Test_methods[] = {
    {"func", NULL, METH_VARARGS, "Target function alias."},
    {"func_x", (PyCFunction)func_x, METH_NOARGS, "Target function X." },
    {"func_y", (PyCFunction)func_y, METH_NOARGS, "Target function Y."},
    {NULL}  /* Sentinel */
};


/* Definition of the class instance objects. */
static PyMemberDef Test_members[] = {
    {"var", T_OBJECT_EX, offsetof(Test, var), 0, "The function choice."},
    {NULL}  /* Sentinel */
};


/* Class destruction. */
static void Test_dealloc(Test *self)
{
    Py_XDECREF(self->var);
    #if PY_MAJOR_VERSION >= 3
        Py_TYPE(self)->tp_free((PyObject*)self);
    #else
        self->ob_type->tp_free((PyObject*)self);
    #endif
}


/* The Test.__getattr__() instance method for obtaining instance attributes. */
static PyObject *
Test_getattro(Test *self, PyObject *object)
{
    PyObject *objname_bytes, *result = NULL;
    char *objname, *var;

    /* The variable name and object name. */
    #if PY_MAJOR_VERSION >= 3
        var = PyBytes_AsString(self->var);
        objname_bytes = PyUnicode_AsEncodedString(object, "utf-8", "strict");
        objname = PyBytes_AsString(objname_bytes);
    #else
        var = PyString_AsString(self->var);
        objname = PyString_AsString(object);
    #endif

    /* Target function aliasing. */
    if (strcmp(objname, "func") == 0) {
        if (strcmp(var, "x") == 0)
            result = PyObject_GetAttrString((PyObject *)self, "func_x");
        else
            result = PyObject_GetAttrString((PyObject *)self, "func_y");
    }

    /* Normal attribute handling (nothing else to return). */
    else
        result = PyObject_GenericGetAttr((PyObject *)self, object);

    return result;
}


/* The Test.__new__() method definition for creating the class. */
static PyObject *
Test_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    Test *self;

    self = (Test *)type->tp_alloc(type, 0);
    if (self != NULL) {
        /* Set up the function choice variable to the empty string. */
        #if PY_MAJOR_VERSION >= 3
            self->var = PyUnicode_FromString("");
        #else
            self->var = PyString_FromString("");
        #endif
    }

    return (PyObject *)self;
}


/* The Test.__init__() method definition for initialising the class. */
static int
Test_init(Test *self, PyObject *args, PyObject *keywords)
{
    PyObject *var=NULL, *tmp;

    /* The keyword list. */
    static char *keyword_list[] = {"var", NULL};

    /* Parse the function arguments. */
    if (! PyArg_ParseTupleAndKeywords(args, keywords, "|S", keyword_list, &var))
        return -1;

    /* Store the arguments in self. */
    if (var) {
        tmp = self->var;
        Py_INCREF(var);
        self->var = var;
        Py_XDECREF(tmp);
    }

    return 0;
}


/* Define the type object to create the class. */
static PyTypeObject Test_type = {
    #if PY_MAJOR_VERSION >= 3
        PyVarObject_HEAD_INIT(NULL, 0)
    #else
        PyObject_HEAD_INIT(NULL)
        0,                                  /*ob_size*/
    #endif
    "test.Test",                            /*tp_name*/
    sizeof(Test),                           /*tp_basicsize*/
    0,                                      /*tp_itemsize*/
    (destructor)Test_dealloc,               /*tp_dealloc*/
    0,                                      /*tp_print*/
    0,                                      /*tp_getattr*/
    0,                                      /*tp_setattr*/
    0,                                      /*tp_compare*/
    0,                                      /*tp_repr*/
    0,                                      /*tp_as_number*/
    0,                                      /*tp_as_sequence*/
    0,                                      /*tp_as_mapping*/
    0,                                      /*tp_hash */
    0,                                      /*tp_call*/
    0,                                      /*tp_str*/
    (getattrofunc)Test_getattro,            /*tp_getattro*/
    0,                                      /*tp_setattro*/
    0,                                      /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,    /*tp_flags*/
    "Test class.",                          /* tp_doc */
    0,                                      /* tp_traverse */
    0,                                      /* tp_clear */
    0,                                      /* tp_richcompare */
    0,                                      /* tp_weaklistoffset */
    0,                                      /* tp_iter */
    0,                                      /* tp_iternext */
    Test_methods,                           /* tp_methods */
    Test_members,                           /* tp_members */
    0,                                      /* tp_getset */
    0,                                      /* tp_base */
    0,                                      /* tp_dict */
    0,                                      /* tp_descr_get */
    0,                                      /* tp_descr_set */
    0,                                      /* tp_dictoffset */
    (initproc)Test_init,                    /* tp_init */
    0,                                      /* tp_alloc */
    Test_new,                               /* tp_new */
};


/* Define the Python 3 module. */
#if PY_MAJOR_VERSION >= 3
    static PyModuleDef moduledef = {
        PyModuleDef_HEAD_INIT,
        "test",       /* m_name */
        "C module.",  /* m_doc */
        -1,           /* m_size */
        NULL,         /* m_reload */
        NULL,         /* m_traverse */
        NULL,         /* m_clear */
        NULL,         /* m_free */
    };
#endif


/* Declarations for DLL import/export */
#ifndef PyMODINIT_FUNC
#define PyMODINIT_FUNC void
#endif


/* Initialise as a Python module. */
PyMODINIT_FUNC
#if PY_MAJOR_VERSION >= 3
    PyInit_test(void)
    {
        PyObject* m;

        if (PyType_Ready(&Test_type) < 0)
            return NULL;

        m = PyModule_Create(&moduledef);
        if (m == NULL)
            return NULL;

        Py_INCREF(&Test_type);
        PyModule_AddObject(m, "Test", (PyObject *)&Test_type);

        return m;
    }
#else
    inittest(void)
    {
        PyObject* m;

        if (PyType_Ready(&Test_type) < 0)
            return;

        m = Py_InitModule3("test", test_methods,
                           "Example module that creates an extension type.");
        if (m == NULL)
            return;

        Py_INCREF(&Test_type);
        PyModule_AddObject(m, "Test", (PyObject *)&Test_type);
    }
#endif

对于这样的问题,这是一个很好的解决方法。它可能也是最简单的实现方法。然而,有一个缺点——速度。在Python示例中,类方法别名在类实例化期间发生。因此,该逻辑只出现一次,并且可以多次调用
func()。在我的问题中,
func()。在这种情况下,最好在类实例化过程中使用函数切换逻辑。不管怎样,这种变通方法比Python代码快得多。如果需要额外的速度,您可以调用并存储指向
func_a
func_b
的指针,而不是标记。
cc $(python-config --cflags --ldflags) -o test.os -c -fPIC test.c
cc -o test.so -shared test.os