如何构建包含Cython代码的Python包结构

131 投票
10 回答
30674 浏览
提问于 2025-04-16 08:53

我想制作一个包含一些Cython 代码的Python包。我已经把Cython代码搞定了。不过,现在我想知道怎么最好地打包它。

对于大多数只想安装这个包的人,我想包含Cython生成的.c文件,并安排setup.py来编译这个文件,生成模块。这样用户就不需要安装Cython就能安装这个包。

但是对于那些可能想修改这个包的人,我也想提供Cython的.pyx文件,并且以某种方式让setup.py使用Cython来构建它们(所以这些用户就需要安装Cython)。

我应该如何组织包里的文件,以便同时满足这两种情况呢?

Cython的文档给出了一些指导。但它没有说明如何制作一个单一的setup.py来处理有Cython和没有Cython的情况。

10 个回答

21

http://docs.cython.org/en/latest/src/userguide/source_files_and_compilation.html#distributing-cython-modules

强烈建议你在发布你的模块时,除了包含Cython源代码外,还要把生成的.c文件一起分发。这样用户就可以安装你的模块,而不需要先安装Cython。

另外,建议你在发布的版本中默认不要启用Cython编译。即使用户已经安装了Cython,他们可能也不想为了安装你的模块而去使用它。而且,用户安装的Cython版本可能和你使用的版本不同,这样可能会导致编译出错。

这就是说,你随模块一起提供的setup.py文件应该只是一个普通的distutils文件,专门针对生成的.c文件。对于基本示例,我们应该这样做:

from distutils.core import setup
from distutils.extension import Extension
 
setup(
    ext_modules = [Extension("example", ["example.c"])]
)
21

补充一下Craig McQueen的回答:下面是如何重写sdist命令,让Cython在创建源代码包之前自动编译你的源文件。

这样一来,你就不必担心不小心分发了过时的C源代码。如果你对分发过程的控制有限,比如在持续集成中自动创建分发包时,这个方法也会很有帮助。

from distutils.command.sdist import sdist as _sdist

...

class sdist(_sdist):
    def run(self):
        # Make sure the compiled Cython files in the distribution are up-to-date
        from Cython.Build import cythonize
        cythonize(['cython/mycythonmodule.pyx'])
        _sdist.run(self)
cmdclass['sdist'] = sdist
80

我现在自己做了这个,创建了一个Python包叫做 simplerandomBitBucket 仓库 - 更新:现在在 GitHub)。我并不指望这个包会很受欢迎,但这是一个学习Cython的好机会。

这个方法依赖于这样一个事实:使用 Cython.Distutils.build_ext 来构建 .pyx 文件时(至少在Cython 0.14版本中),总是会在和源 .pyx 文件同一个目录下生成一个 .c 文件。

下面是一个简化版的 setup.py,我希望能展示出其中的要点:

from distutils.core import setup
from distutils.extension import Extension

try:
    from Cython.Distutils import build_ext
except ImportError:
    use_cython = False
else:
    use_cython = True

cmdclass = {}
ext_modules = []

if use_cython:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.pyx"]),
    ]
    cmdclass.update({'build_ext': build_ext})
else:
    ext_modules += [
        Extension("mypackage.mycythonmodule", ["cython/mycythonmodule.c"]),
    ]

setup(
    name='mypackage',
    ...
    cmdclass=cmdclass,
    ext_modules=ext_modules,
    ...
)

我还编辑了 MANIFEST.in 文件,以确保 mycythonmodule.c 被包含在源代码分发中(这个源代码分发是通过 python setup.py sdist 创建的):

...
recursive-include cython *
...

我不会把 mycythonmodule.c 提交到版本控制的主干(对于Mercurial来说是'默认')。当我发布新版本时,我需要记得先运行 python setup.py build_ext,以确保 mycythonmodule.c 是最新的,并且包含在源代码分发中。我还会创建一个发布分支,并把C文件提交到这个分支。这样我就有了一个历史记录,记录了与该版本一起分发的C文件。

撰写回答