How should I structure a Python package that contains Cython code

后端 未结 10 1840
傲寒
傲寒 2020-12-22 15:11

I\'d like to make a Python package containing some Cython code. I\'ve got the the Cython code working nicely. However, now I want to know how best to package it.

For

10条回答
  •  Happy的楠姐
    2020-12-22 15:18

    I think I found a pretty good way of doing this by providing a custom build_ext command. The idea is the following:

    1. I add the numpy headers by overriding finalize_options() and doing import numpy in the body of the function, which nicely avoids the problem of numpy not being available before setup() installs it.

    2. If cython is available on the system, it hooks into the command's check_extensions_list() method and by cythonizes all out-of-date cython modules, replacing them with C extensions that can later handled by the build_extension() method. We just provide the latter part of the functionality in our module too: this means that if cython is not available but we have a C extension present, it still works, which allows you to do source distributions.

    Here's the code:

    import re, sys, os.path
    from distutils import dep_util, log
    from setuptools.command.build_ext import build_ext
    
    try:
        import Cython.Build
        HAVE_CYTHON = True
    except ImportError:
        HAVE_CYTHON = False
    
    class BuildExtWithNumpy(build_ext):
        def check_cython(self, ext):
            c_sources = []
            for fname in ext.sources:
                cname, matches = re.subn(r"(?i)\.pyx$", ".c", fname, 1)
                c_sources.append(cname)
                if matches and dep_util.newer(fname, cname):
                    if HAVE_CYTHON:
                        return ext
                    raise RuntimeError("Cython and C module unavailable")
            ext.sources = c_sources
            return ext
    
        def check_extensions_list(self, extensions):
            extensions = [self.check_cython(ext) for ext in extensions]
            return build_ext.check_extensions_list(self, extensions)
    
        def finalize_options(self):
            import numpy as np
            build_ext.finalize_options(self)
            self.include_dirs.append(np.get_include())
    

    This allows one to just write the setup() arguments without worrying about imports and whether one has cython available:

    setup(
        # ...
        ext_modules=[Extension("_my_fast_thing", ["src/_my_fast_thing.pyx"])],
        setup_requires=['numpy'],
        cmdclass={'build_ext': BuildExtWithNumpy}
        )
    

提交回复
热议问题