Skip to content
New issue

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

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

Already on GitHub? Sign in 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

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

No branches or pull requests

5 participants