Reputation: 15217
I'm writing functools.partial
object alternative, that accumulates arguments until their number become sufficient to make a call.
I use C API and I have tp_call
implementation which when its called, returns modified version of self or PyObject*
.
At first I followed Defining New Types guide and then realized, that I just can't return different types (PyObject *
and MyObject*
) from tp_call
implementation.
Then I tried to not use struct
with MyObject*
definition and use PyObject_SetAttrString
in tp_init
instead, just like we do that in Python. But in that case I got AttributeError
, because you can't set arbitrary attributes on object
instances in Python.
What I need here is to make my tp_call
implementation polymorphic, and make it able to return either MyObject
which is subclass of PyObject
, or PyObject
type itself.
What is the sane way to do that?
That's the code:
static PyObject *Curry_call(Curry *self, PyObject *args,
PyObject *kwargs) {
PyObject * old_args = self->args;
self->args = PySequence_Concat(self->args, args);
Py_DECREF(old_args);
if (self->kwargs == NULL && kwargs != NULL) {
self->kwargs = kwargs;
Py_INCREF(self->kwargs);
} else if (self->kwargs != NULL && kwargs != NULL) {
PyDict_Merge(self->kwargs, kwargs, 1);
}
if ((PyObject_Size(self->args) +
(self->kwargs != NULL ? PyObject_Size(self->kwargs) : 0)) >=
self->num_args) {
return PyObject_Call(self->fn, self->args, self->kwargs);
} else {
return (PyObject *)self;
}
}
Why I initially abandoned this implementation - because I get segfault with it on subsequent calls of partial object. I thought that It happens because of casting Curry *
to PyObject*
issues. But now I have fixed the segfault by adding Py_INCREF(self);
before return (PyObject *)self;
. Very strange to me. Should I really INCREF self if I return it by C API ownership rules?
Upvotes: 1
Views: 290
Reputation: 36433
I don't really know your whole code, but as long as MyObject
is a PyObject
(compatible, i.e. has the same "header" fields, make sure you have a length field), CPython is designed to just take your MyObject as a PyObject; simply cast the pointer to PyObject before returning it.
As you can see here, that is one of the things that is convenient when using C++: You can actually have subclasses with type safety, and you don't have to worry about someone just copying over half of your subclass' instance, for example.
EDIT: because it was asked "isn't this unsafe": yes. It is. But its only as unsafe as type handling in user code gets; CPython lets you do this, because it stores and checks the PyTypeObject *ob_type
member of the PyObject
struct contained. That's about as safe as for example C++'s runtime type checking is -- but it's implemented by python developers as opposed to GCC/clang/MSVC/icc/... developers.
Upvotes: 1
Reputation: 282026
If you've defined your MyObject
type correctly, you should be able to simply cast your MyObject *
to a PyObject *
and return that. The first member of a MyObject
is a PyObject
, and C lets you cast a pointer to a struct to a pointer to the struct's first member and vice versa. I believe the feature exists specifically to allow things like this.
Upvotes: 2