3

In pure Python, it is relatively simple to define and use a metaclass.

class Meta(type):
    def __new__(cls, name, bases, dict):
        x = super().__new__(cls, name, bases, dict)
        print("I'm called on class construction time!")
        return x

class A(metaclass=Meta):
    pass

class B(A):
    pass

How do you define this metaclass from a Python C extension?

1 Answer 1

3
  • Define the metaclass a subclass (tp_base) of PyType_Type
  • Place the __new__ logic in tp_init
  • Use the metaclass in other classes by referencing it in PyVarObject_HEAD_INIT
#include <Python.h>
#include <iostream>

struct foometa {
  PyTypeObject head;
};

int foometa_init(foometa *cls, PyObject *args, PyObject *kwargs) {
  if (PyType_Type.tp_init((PyObject*)cls, args, kwargs) < 0) {
    return -1;
  }
  std::cerr << "I'm called on class construction time!\n";
  return 0;
}

#define DEFERRED_ADDRESS(ADDR) nullptr

static PyTypeObject foometa_type = {
    PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0)
    "demo.foometa",
    0,
    0,
    0,                                          /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    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 | Py_TPFLAGS_BASETYPE, /* 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 */
    DEFERRED_ADDRESS(&PyType_Type),             /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)foometa_init,                    /* tp_init */
    0,                                          /* tp_alloc */
    0                                           /* tp_new */
};

PyObject *fooparent_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
  PyObject* obj = type->tp_alloc(type, 0);
  return obj;
}

static PyTypeObject fooparent_type = {
    PyVarObject_HEAD_INIT(&foometa_type, 0)
    "demo.fooparent",
    sizeof(PyObject),
    0,
    0,                                          /* tp_dealloc */
    0,                                          /* tp_print */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_reserved */
    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 | Py_TPFLAGS_BASETYPE, /* 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 */
    0,             /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                    /* tp_init */
    0,                                          /* tp_alloc */
    fooparent_new                                           /* tp_new */
};

int
demo_init(PyObject *m) {
  foometa_type.tp_base = &PyType_Type;
  if (PyType_Ready(&foometa_type) < 0) {
    return -1;
  }
  if (PyType_Ready(&fooparent_type) < 0) {
    return -1;
  }

  Py_INCREF(&foometa_type);
  if (PyModule_AddObject(m, "foometa",
                         (PyObject *) &foometa_type) < 0)
      return -1;

  Py_INCREF(&fooparent_type);
  if (PyModule_AddObject(m, "fooparent",
                         (PyObject *) &fooparent_type) < 0)
      return -1;
  return 0;
}

static PyModuleDef demomodule = {
    PyModuleDef_HEAD_INIT,
    "demo",
    "Example module",
    -1,
    NULL, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_demo() {
  PyObject* m = PyModule_Create(&demomodule);
  if (m == nullptr) return nullptr;
  if (demo_init(m) < 0) return nullptr;
  return m;
}

Full example runnable at https://github.com/ezyang/cpython-metaclass

Sign up to request clarification or add additional context in comments.

4 Comments

There's a bug in this code where if you subclass from fooparent twice you will fail an assert Modules/gcmodule.c:714: handle_weakrefs: Assertion "wr->wr_object == op" failed
Bug is fixed, I think.
The foometa struct doesn't have enough fields, interestingly enough, so it might be defined wrong.
tp_init is the C equivalent of __init__, not __new__. __new__ logic should go in tp_new.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.