Cosmo
Cosmo

Reputation: 930

Python C API: How to check if an object is an instance of a type

I want to check if an object is an instance of a certain class. In Python I can do this with isinstance(obj, cls). In C/C++, I found a function named PyObject_IsInstance. But it seems not to work like isinstance.

In detail (also described as sample codes below):

  1. In C++ I defined my custom type My. The type definition is MyType and the object definition is MyObject.
  2. Add MyType to the exported module with name My.
  3. In Python, create a new instance my = My(), and isinstance(my, My) returns True.
  4. While in C++ we use PyObject_IsInstance(my, (PyObject*)&MyType) to check my, and this returns 0, which means my is not an instance of the class defined by MyType.

Full C++ code:

#define PY_SSIZE_T_CLEAN
#include <python3.6/Python.h>
#include <python3.6/structmember.h>
#include <stddef.h>

typedef struct {
    PyObject_HEAD
    int num;
} MyObject;

static PyTypeObject MyType = []{
    PyTypeObject ret = {
        PyVarObject_HEAD_INIT(NULL, 0)
    };
    ret.tp_name = "cpp.My";
    ret.tp_doc = NULL;
    ret.tp_basicsize = sizeof(MyObject);
    ret.tp_itemsize = 0;
    ret.tp_flags = Py_TPFLAGS_DEFAULT;
    ret.tp_new = PyType_GenericNew;
    return ret;
}();

// check if obj is an instance of MyType
static PyObject *Py_fn_checkMy(PyObject *obj) {
    if (PyObject_IsInstance(obj, (PyObject *)&MyType)) Py_RETURN_TRUE;
    else Py_RETURN_FALSE;
}

static PyMethodDef modmethodsdef[] = {
    { "checkMy", (PyCFunction)Py_fn_checkMy, METH_VARARGS, NULL },
    { NULL }
};

static PyModuleDef moddef = []{
    PyModuleDef ret = {
        PyModuleDef_HEAD_INIT
    };
    ret.m_name = "cpp";
    ret.m_doc = NULL;
    ret.m_size = -1;
    return ret;
}();

PyMODINIT_FUNC
PyInit_cpp(void)
{
    PyObject *mod;
    if (PyType_Ready(&MyType) < 0)
        return NULL;
    mod = PyModule_Create(&moddef);
    if (mod == NULL)
        return NULL;
    Py_INCREF(&MyType);
    PyModule_AddObject(mod, "My", (PyObject *)&MyType);
    PyModule_AddFunctions(mod, modmethodsdef);
    return mod;
}

Compile this into cpp.so, and test it in Python:

>>> import cpp
>>> isinstance(cpp.My(), cpp.My)
True
>>> cpp.checkMy(cpp.My())
False

Upvotes: 8

Views: 5643

Answers (1)

DavidW
DavidW

Reputation: 30899

METH_VARARGS

This is the typical calling convention, where the methods have the type PyCFunction. The function expects two PyObject* values. The first one is the self object for methods; for module functions, it is the module object. The second parameter (often called args) is a tuple object representing all arguments. This parameter is typically processed using PyArg_ParseTuple() or PyArg_UnpackTuple().

The function signature of Py_fn_checkMy does not match this. It should take two arguments. The first is the module, and this is what you are checking against MyType. The second argument (which you don't actually accept) is a tuple containing the object you passed. You should extract the argument from the tuple and check the type of this.

You'd probably be better using METH_O to specify a single argument instead of extracting arguments from a tuple:

static PyObject *Py_fn_checkMy(PyObject *self, PyObject *obj) {
    if (PyObject_IsInstance(obj, (PyObject *)&MyType)) Py_RETURN_TRUE;
    else Py_RETURN_FALSE;
}


static PyMethodDef modmethodsdef[] = {
    { "checkMy", (PyCFunction)Py_fn_checkMy, METH_O, NULL },
    { NULL }
};

Upvotes: 6

Related Questions