Passing arguments to tp_new and tp_init from subtypes in Python C API

天涯浪子 提交于 2019-12-03 06:26:19

The problem is that PyArgs_ParseTupleAndKeywords doesn't provide a way to extract extra *args and **kwargs from the input args and keywords; indeed, any extra arguments result in a TypeError; "Function takes %s %d positional arguments (%d given)", or "'%U' is an invalid keyword argument for this function".

This means that you're going to have to parse args and keywords yourself; you're guaranteed that args is a tuple and keywords is a dict, so you can use the standard methods (PyTuple_GET_ITEM and PyDict_GetItemString) to extract the arguments you're interested in, and identify and construct a tuple and dict to pass on from the remainder. You obviously can't modify args, because tuples are immutable; and while popping items from keywords should be OK it does seem a little risky (example crash).

A more ambitious but definitely feasible route would be to copy vgetargskeywords from getargs.c (http://hg.python.org/cpython/file/tip/Python/getargs.c) and extend it to take optional out-parameters for remainder *args and **kwargs. This should be fairly straightforward as you just need to modify the parts where it detects and throws TypeError on extra arguments (extra args; extra keywords). Good luck if you choose this route.

pgpython

OK, to answer your first question: first of all you have a C Structure representing your class object, but from a C side. So in a header file called Shapes.h

typedef struct {
PyObject_HEAD
char *shapename;
} Shape;

typedef struct {
PyObject_HEAD
    char *shapename;
char *color;
} ColouredShape;

The things to note here are the following:

  • shapename and colour are effectively private variables. Python can't see them or interact with them
  • ColouredShape has to define all the parameters of Shape for inheritace to work as well as appearing in the same order

Next we need a type for our classes. The type basically defines the object from a Python point of view, i.e., its methods, members and any special methods its has. It's also where we tell Python that ColouredShape is a subclass of Shape. Its looks like the following:

PyTypeObject ShapeType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"mymod.Shape", /*tp_name*/
sizeof(ShapeType), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) SimpleParameter_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*/
Shape__str__, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"A class representing a Shape", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Shape_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) Shape_init, /* tp_init */
0, /* tp_alloc */
Shape_new, /* tp_new */
};

PyTypeObject ColouredShapeType = {
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"mymod.ColouredShape", /*tp_name*/
sizeof(ColouredShape), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor) ColouredShape_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*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
"A class representing a coloured shape", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
ColouredShape_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
&ShapeType, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc) ColouredShape_init, /* tp_init */
0, /* tp_alloc */
ColouredShape_new, /* tp_new */
};

One important point to note is that mymod must be the name of your Python C extension as imported from Python. In answer to your second question, your init function would look as follows:

int Shape_init(Shape *self, PyObject *args, PyObject *kwds){
   char *colour = null;
   static char *kwdlist[] = {"colour", NULL};
   if (!PyArg_ParseTupleAndKeywords(args, kwds, "s", kwdlist,
&colour)){
return -1;
}
   //Initialise your object here
}

There is no reason why ColouredShape_init cannot call Shape_init. However my understanding of PyArgs_ParseTupleAndKeywords is:

  • There is no format parameter for the rest of your positional and keyword arguments
  • It does not alter your args and keywords

Which is where your difficulty will be if you tried to go that way.

If you have any further questions on it let me know. But I suggest you look at the PyArgs_ParseTupleAndKeywords to get a better understanding of it

I have delved into the python c api at some length and its a strange beast to say the least. It may be worth looking into pyrex or cython which take care of a lot of the ugliness for you. From a python perspective there is no reason you can change arguments. however you can't change the order of members in C so for consistency I strongly advise you not to do it. If you want help on how to achieve this without pyrex or cython let me know. but i should warn it takes some understanding and a lot of boiler plate code to understand

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!