Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Add way to set metaclass for #[pyclass] #906

Open
programmerjake opened this issue May 6, 2020 · 4 comments
Open

Add way to set metaclass for #[pyclass] #906

programmerjake opened this issue May 6, 2020 · 4 comments

Comments

@programmerjake
Copy link
Contributor

apparently it works by setting the ob_type member to a custom instance (the metaclass) of a class that derives from type.

@lausek
Copy link

lausek commented Sep 22, 2020

Has there been progress on this issue? I'm looking for a way of implementing a custom instancecheck for a pyclass.

@davidhewitt
Copy link
Member

Any progress would have been on this issue! A proposal of how the API should look and / or a PR to implement this is always welcome 😄

@jovenlin0527
Copy link
Contributor

Someone mentioned this issue on Gitter. https://bugs.python.org/issue15870

It seems that metaclass is not officially supported in C extensions.

@mbway
Copy link

mbway commented Oct 5, 2024

Since python 3.12 there is a function: PyType_FromMetaclass (docs) which allows metaclasses to be set in extension modules. Below I have a c example (with error handling and memory cleanup removed for brevity) where I create MyMetaclass with a __getitem__ equivalent and create a class MyClass with MyMetaclass as its metaclass. You can then do MyClass[123] and have it call __getitem__ of the metaclass.

This could be used to implement more features that EnumType provides (as requested here: #2887).

In addition to the official way of doing things, I found that I was able to set the metaclass of a class by assigning to ob_type after calling PyTypeReady as suggested here but that does seem to be a hack and might cause issues.

#define PY_SSIZE_T_CLEAN
#include <Python.h>

typedef struct { PyTypeObject base_type; } MyMetaclass;

static PyMethodDef MyMetaclass_methods[] = { {NULL} };

static PyObject *myclass_getitem(PyObject *self, PyObject *key) { return PyLong_FromLong(123); }

static PyMappingMethods MyMetaClassMappingMethods = {
    .mp_length = NULL,
    .mp_subscript = myclass_getitem,
    .mp_ass_subscript = NULL,
};

static PyTypeObject MyMetaclassType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    .tp_name = "metaclass_test.MyMetaclass",
    .tp_basicsize = sizeof(MyMetaclass),
    .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
    .tp_as_mapping = &MyMetaClassMappingMethods,
    .tp_base = &PyType_Type,
    .tp_methods = MyMetaclass_methods,
};

typedef struct { PyObject_HEAD } MyClass;

static PyMethodDef MyClass_methods[] = { {NULL} };

PyTypeObject *create_myclass_type(PyObject *module, PyTypeObject *metaclass) {
    static PyType_Slot slots[] = {
        {Py_tp_methods, MyClass_methods},
        { 0, NULL }
    };
    PyType_Spec spec = {
        .name = "metaclass_test.MyClass",
        .basicsize = sizeof(MyClass),
        .itemsize = 0,
        .flags = Py_TPFLAGS_DEFAULT,
        .slots = slots,
    };
    return (PyTypeObject*)PyType_FromMetaclass(metaclass, module, &spec, NULL);
}

static PyModuleDef metaclass_test_module = {
    PyModuleDef_HEAD_INIT,
    .m_name = "metaclass_test",
    .m_doc = "an example module",
    .m_size = -1,
};

PyMODINIT_FUNC PyInit_metaclass_test(void)
{
    PyObject *m = PyModule_Create(&metaclass_test_module);
    PyType_Ready(&MyMetaclassType);
    PyModule_AddObject(m, "MyMetaclass", (PyObject *)&MyMetaclassType);
    PyTypeObject *MyClassType = create_myclass_type(m, &MyMetaclassType);
    PyModule_AddObject(m, "MyClass", (PyObject*)MyClassType);
    return m;
}

Output:

>>> from metaclass_test import MyClass, MyMetaclass
>>> type(MyClass)
<class 'metaclass_test.MyMetaclass'>
>>> MyClass['foo']
123
>>> MyMetaclass.__getitem__(MyClass, 'foo')
123

# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

No branches or pull requests

5 participants