I am currently writing a python extension which contains a number of classes. I have encountered an unexpected behavior when compiling my extension against python 3.8 (it works fine in python 2.7). When my custom type definition uses a metaclass and provides a static method(s), such static method becomes difficult to call. Calling simply Foo.bar() causes a TypeError: 'staticmethod' object is not callable exception. I have produced a minimal example which reproduces that behavior in python 3.8:
#include <Python.h>
static PyModuleDef foomodule = {
PyModuleDef_HEAD_INIT,
"foo",
"Foo module",
-1, 0, 0, 0, 0
};
struct foo_FooObject {
PyObject_HEAD
};
static PyObject* foo_Foo_bar( PyObject *self, PyObject *args )
{
return self;
}
static PyMethodDef fooMethods[] = {
{ "bar", foo_Foo_bar, METH_STATIC | METH_VARARGS, "Foo.bar()" },
{ NULL, NULL, 0, NULL }
};
static PyTypeObject fooMetaType = {
PyVarObject_HEAD_INIT( &PyType_Type, 0 )
"Foometa",
sizeof(PyTypeObject),
0,
0, // 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, // tp_flags
0, // tp_doc
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
0, // tp_methods
0, // tp_members
0, // tp_getset
&PyType_Type, // tp_base
0, // tp_dict
0, // tp_descr_get
0, // tp_descr_set
0, // tp_dictoffset
0, // tp_init
0, // tp_alloc
0, // tp_new
0, // tp_free
0, // tp_is_gc
0, // tp_bases
0, // tp_mro
0, // tp_cache
0, // tp_subclasses
0 // tp_weaklist
, 0 // tp_del
, 0 // tp_version_tag
, 0 // tp_finalize
};
static PyTypeObject foo_FooType = {
PyVarObject_HEAD_INIT(&fooMetaType, 0)
"foo.Foo", /* tp_name */
sizeof(foo_FooObject), /* tp_basicsize */
0, /* tp_itemsize */
0, /* 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, /* tp_flags */
"foo.Foo object", /* tp_doc */
0, // tp_traverse
0, // tp_clear
0, // tp_richcompare
0, // tp_weaklistoffset
0, // tp_iter
0, // tp_iternext
0, // 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
0, // tp_init
0, // tp_alloc
0, // tp_new
0, // tp_free
0, // tp_is_gc
0, // tp_bases
0, // tp_mro
0, // tp_cache
0, // tp_subclasses
0 // tp_weaklist
, 0 // tp_del
, 0 // tp_version_tag
, 0 // tp_finalize
};
PyMODINIT_FUNC PyInit_foo()
{
PyObject* foo = PyModule_Create( &foomodule );
if ( foo == NULL ) {
return NULL;
}
if ( PyType_Ready( &fooMetaType ) < 0 ) {
return NULL;
}
foo_FooType.tp_methods = fooMethods;
if ( PyType_Ready( &foo_FooType ) < 0 ) {
return NULL;
}
Py_INCREF( &foo_FooType );
PyModule_AddObject( foo, "Foo", (PyObject *)&foo_FooType );
return foo;
}
After compiling the extension and importing it, I would expect to be able to say:
from foo import Foo
Foo.bar()
It works fine if I do not use metaclass, (ie.replace the &fooMetaType with NULL in the foo_FooType definition). Am I doing something wrong here? Can I even combine metaclasses with static methods, or is this not possible for some reason?