Search code examples
cythoncpythonpython-internals

Run cython code when extension module gets garbage collected


Is there a way to register a function within an Cython extenstion module such that this function is called when the Python garbage collector destroys the module object ?

Something like

def __dealloc__(...)

on module level ?


Solution

  • I'm not sure there is an explicit/official way to achieve that. However, you could add a global variable which will be destroyed, when the cython module is deallocated:

    # foo.pyx:
    class Observer:
        def __init__(self):
            print("module initialized")
    
        def __del__(self):
            print ("module deleted")
    
    _o=Observer()
    

    However, cythonizing it via cythonize foo.pyx -i will not lead to the desired effect:

    [$] python -c `import foo`
    module initialized
    

    there is no "module deleted" printed to the console!

    The problem: one cannot reload a cython-extension anyway (for example via importlib.reload()), so it gets only deallocated when the Python interpreter is shut down. But then there is no need to deallocate the resources ...

    Cython keeps all global Python-variables in a global C-variable which is called static PyObject *__pyx_d; in the cythonized C-source; this is just a Python-dictionary. However, because there is a reference to the global _o in this not destroyed dictionary, the reference count of _o will never become 0 an thus the object doesn't get destroyed.

    By setting generate_cleanup_code=True one can force Cython to generate a clean-up function which will be put into the m_free-slot of the PyModuleDef definition struct. For example with the following setup-file:

    from distutils.core import setup
    
    from Cython.Build import cythonize
    from Cython.Compiler import Options
    
    Options.generate_cleanup_code = True  #  this is important!
    
    setup(
        name = "foo",
        ext_modules = cythonize("foo.pyx"),
    )
    

    And now after python setup.py build_ext -i:

    [$] python -c "import foo"
    module initialized
    module deleted
    

    it works as expected.


    Because there is no reload for Cython-extensions, "module deleted" will be only seen when module_dealloc is called.

    However, when the first listing were pure-Python, we would also see module deleted if importlib.reload(foo) were called. In this case "module_dealloc" isn't used, but all references to global _o would be cleared and object would be destroyed.

    Depending on what one want, it might be either the expected or unexpected behavior.