使用 cython 创建包,这样用户就可以在没有安装 cython 的情况下安装它

Create package with cython so users can install it without having cython already installed

我有一个问题。我想分发我的 cython 驱动的包,但我看不到在 setup.py 中构建它们的简单方法。我想 setup.py 到:

目前在我痒痒的包里,我正在使用这个相当复杂的代码:

import os
from glob import glob
from distutils.command.build_ext import build_ext as _build_ext
from distutils.command.sdist import sdist as _sdist
from distutils.core import setup
from distutils.core import Extension



def generate_extensions():
    return [
        # Compile cython-generated .c files into importable .so libraries.
        Extension(os.path.splitext(name)[0], [name])
        for name in C_FILES
    ]


# In distribution version, there are no pyx files, when you clone package from git, there will be no c files.
CYTHON_FILES = glob('itchy/*.pyx')
C_FILES = glob('itchy/*.c')
extensions = generate_extensions()


class build_ext(_build_ext):
    def run(self):
        # Compile cython files (.pyx, some of the .py) into .c files if Cython is available.
        try:
            from Cython.Build import cythonize
            if CYTHON_FILES:
                cythonize(CYTHON_FILES)

                # Update C_FILES in case they were originally missing.
                global C_FILES, extensions
                C_FILES = glob('itchy/*.c')
                extensions = generate_extensions()
            else:
                print('No .pyx files found, building extensions skipped. Pre-built versions will be used.')
        except ImportError:
            print('Cython is not installed, building extensions skipped. Pre-built versions will be used.')
            assert C_FILES, 'C files have to be present in distribution or Cython has to be installed'
        _build_ext.run(self)


class sdist(_sdist):
    def run(self):
        # Make sure the compiled Cython files in the distribution are up-to-date
        self.run_command("build_ext")
        _sdist.run(self)


setup(
    (...)
    ext_modules = extensions,
    cmdclass = {
        'build_ext': build_ext,
        'sdist': sdist,
    },
)

通常通过尝试导入 cython 并将扩展调整为

  1. 如果存在 cython,则使用 cython 构建 pyx 文件
  2. 如果 cython 不存在,构建 C 文件

例如:

try:
    from Cython.Distutils.extension import Extension
    from Cython.Distutils import build_ext
except ImportError:
    from setuptools import Extension
    USING_CYTHON = False
else:
    USING_CYTHON = True

ext = 'pyx' if USING_CYTHON else 'c'
sources = glob('my_module/*.%s' % (ext,))
extensions = [
    Extension(source.split('.')[0].replace(os.path.sep, '.'),
              sources=[source],
    )
for source in sources]
cmdclass = {'build_ext': build_ext} if USING_CYTHON else {}

setup(<..>, ext_modules=extensions, cmdclass=cmdclass)

需要 source.split 东西,因为 cythonized 扩展名需要采用 my_module.ext 形式,而 glob 需要像 my_module/ext.

这样的路径名

See this repository 一个真实世界的例子。

但是,您应该在 git 存储库和可分发文件中包含 .c 文件,否则当需要构建分发时,.c 文件将被重新构建的文件可能与在您的计算机上构建的文件相同,也可能不同。

例如,它们可能由另一个版本的 cython 构建,或者在不同的平台上生成不同的代码。

Cython 是一个静态编译器 - 建议将其生成的文件提交到存储库。

It is strongly recommended that you distribute the generated .c files as well as your Cython sources, so that users can install your module without needing to have Cython available.

See Cython documentation on distributing modules.