[capi-sig] Clarifying tp_new() and tp_init() arguments and PEP-253
Mateusz Loskot
mateusz at loskot.net
Fri Jun 8 13:52:16 CEST 2012
Hi,
I'm reading PEP-253 on Subtyping Built-in Types [1] which by the way has been
extremely helpful to understand details of handling types using Python C API.
In the "Creating a subtype of a built-in type in C" section, the document
includes the following note on the two slots arguments:
"Both tp_new() and tp_init() should receive exactly the same 'args'
and 'kwds' arguments, and both should check that the arguments are
acceptable, because they may be called independently."
I can understand what it says, but I'm unsure about how to interpret it.
Let's consider a simple case of custom type definition,
without subclassing involved.
Does this note recommend to simply repeat the whole args and kwds
parsing and checking in both, tp_new and tp_init?
Like this Noddy example from the Python 3 documentation [2]:
static PyTypeObject noddy_NoddyType = { ... };
int Noddy_init(Noddy* self, PyObject* args, PyObject* kwds);
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
/* check args, initialise Noddy members, etc. */
}
Now as per the PEP-253 "both should check that the arguments are acceptable",
so tp_new gets the very same arguments parsing/checking copied & pasted:
PyObject* Noddy_new(PyTypeObject* type, PyObject* args, PyObject* kwds)
{
PyObject *first=NULL, *last=NULL, *tmp;
static char *kwlist[] = {"first", "last", "number", NULL};
if (! PyArg_ParseTupleAndKeywords(args, kwds, "|OOi", kwlist,
&first, &last,
&self->number))
return -1;
/* Now, what to do with the args?
Ignore?
DO or DO NOT repeat the tp_init work of Noddy members initialization?
*/
Noddy *self;
self = (Noddy *)type->tp_alloc(type, 0);
if (self != NULL) {
self->first = PyUnicode_FromString("");
if (self->first == NULL)
{
Py_DECREF(self);
return NULL;
}
self->last = PyUnicode_FromString("");
if (self->last == NULL)
{
Py_DECREF(self);
return NULL;
}
self->number = 0;
}
return (PyObject *)self;
}
Repeating the question from the comment above,
what tp_new is supposed to do with the arguments?
The Noddy example from the manual simply ignores args and kwds,
it does not even perform the checks recommended by the PEP-253.
What the Noddy_new should look like to conform with the PEP-253
recommendations?
The PEP-253 also includes this recommendation related to my question:
"This should first call the base type's tp_new() slot and
then initialize the subtype's additional data members. To further
initialize the instance, the tp_init() slot is typically called.
Note that the tp_new() slot should *not* call the tp_init() slot;"
In the context of the Noddy example, the "initialize the subtype's additional
data members" means zero-initialisation with empty strings. Makes sense.
Then "To further initialize the instance", tp_init is called of course,
so calling tp_init from tp_new is not advised, this is clear.
So, AFAIU, the PEP-253 suggests to make tp_new check the args and kwds,
report (early) any unacceptable arguments, but not necessarily to
use the values to initialise the instance, as that is tp_init's job.
Does it sound right?
BTW, I understand role of tp_new and tp_init for initialising object would
be different depending if noddy_NoddyType is considered as immutable
object type or not.
But, let's simplify and say noddy_NoddyType is a typical Python object type.
[1] http://www.python.org/dev/peps/pep-0253/
[2] http://docs.python.org/py3k/extending/newtypes.html
Best regards,
--
Mateusz Loskot, http://mateusz.loskot.net
More information about the capi-sig
mailing list