Metaclasses for Cython extension types - Python technicalities

Skip to content
Back to top
View this page
Edit this page

# Metaclasses for Cython extension types

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

Warning

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, and turn off the Cython directive `always_allow_keywords`. This method should return a type to be used as metaclass:

    cimport cython
    cimport sage.cpython.cython_metaclass
    cdef class MyCustomType():
        @cython.always_allow_keywords(False)
        def __getmetaclass__(_):
            from foo import MyMetaclass
            return MyMetaclass

Warning

`__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)`.

Warning

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.

Warning

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.

EXAMPLES:

Sage

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

Python

    >>> from sage.all import *
    >>> cython(                                                                       # needs sage.misc.cython
    ... '''
    ... cimport cython
    ... cimport sage.cpython.cython_metaclass
    ... cdef class MyCustomType():
    ...     @cython.always_allow_keywords(False)
    ...     def __getmetaclass__(_):
    ...         class MyMetaclass(type):
    ...             def __init__(*args):
    ...                 print("Calling MyMetaclass.__init__{}".format(args))
    ...         return MyMetaclass
    ....:
    >>> cdef class MyDerivedType(MyCustomType):
    ...     pass
    ... ''')
    Calling MyMetaclass.__init__(<class '...MyCustomType'>, None, None, None)
    Calling MyMetaclass.__init__(<class '...MyDerivedType'>, None, None, None)
    >>> MyCustomType.__class__                                                        # needs sage.misc.cython
    <class '...MyMetaclass'>
    >>> class MyPythonType(MyDerivedType):                                            # needs sage.misc.cython
    ...     pass
    Calling MyMetaclass.__init__(<class '...MyPythonType'>, 'MyPythonType', (<class '...MyDerivedType'>,), {...})

## Implementation

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.