Metaclasses for Cython extension types

Cython does not support metaclasses, but this module can be used to implement metaclasses for extension types.


This module has many caveats and you can easily get segfaults if you make a mistake. It relies on undocumented Python and Cython behaviour, so things might break in future versions.

How to use

To enable this metaclass mechanism, you need to put cimport sage.cpython.cython_metaclass in your module (in the .pxd file if you are using one).

In the extension type (a.k.a. cdef class) for which you want to define a metaclass, define a method __getmetaclass__ with a single unused argument. This method should return a type to be used as metaclass:

cimport sage.cpython.cython_metaclass
cdef class MyCustomType(object):
    def __getmetaclass__(_):
        from foo import MyMetaclass
        return MyMetaclass

The above __getmetaclass__ method is analogous to __metaclass__ = MyMetaclass in Python 2.


__getmetaclass__ must be defined as an ordinary method taking a single argument, but this argument should not be used in the method (it will be None).

When a type cls is being constructed with metaclass meta, then meta.__init__(cls, None, None, None) is called from Cython. In Python, this would be meta.__init__(cls, name, bases, dict).


The __getmetaclass__ method is called while the type is being created during the import of the module. Therefore, __getmetaclass__ should not refer to any global objects, including the type being created or other types defined or imported in the module (unless you are very careful). Note that non-imported cdef functions are not Python objects, so those are safe to call.

The same warning applies to the __init__ method of the metaclass.


The __new__ method of the metaclass (including the __cinit__ method for Cython extension types) is never called if you’re using this from Cython. In particular, the metaclass cannot have any attributes or virtual methods.


sage: cython('''
....: cimport sage.cpython.cython_metaclass
....: cdef class MyCustomType(object):
....:     def __getmetaclass__(_):
....:         class MyMetaclass(type):
....:             def __init__(*args):
....:                 print("Calling MyMetaclass.__init__{}".format(args))
....:         return MyMetaclass
....: cdef class MyDerivedType(MyCustomType):
....:     pass
....: ''')
Calling MyMetaclass.__init__(<type '...MyCustomType'>, None, None, None)
Calling MyMetaclass.__init__(<type '...MyDerivedType'>, None, None, None)
sage: MyCustomType.__class__
<class '...MyMetaclass'>
sage: class MyPythonType(MyDerivedType):
....:     pass
Calling MyMetaclass.__init__(<class '...MyPythonType'>, 'MyPythonType', (<type '...MyDerivedType'>,), {...})


All this is implemented by defining

#define PyTypeReady(t)  Sage_PyType_Ready(t)

and then implementing the function Sage_PyType_Ready(t) which first calls PyType_Ready(t) and then handles the metaclass stuff.