Gill Bates
Gill Bates

Reputation: 15217

How to achieve polymorphism in Python C API?

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?

UPDATE #0

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;
    }
}

UPDATE #1

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

Answers (2)

Marcus Müller
Marcus Müller

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

user2357112
user2357112

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

Related Questions