我应该如何构造一个包含Cython代码的Python包

我想制作一个包含一些Cython代码的Python包。 我已经得到了很好的Cython代码。 但是,现在我想知道如何打包它。

对于大多数只想安装软件包的人,我想包含Cython创build的.c文件,并安排setup.py来编译生成模块。 然后用户不需要安装Cython来安装软件包。

但是对于那些想要修改软件包的人来说,我也想提供Cython .pyx文件,并且不知何故也允许setup.py使用Cython来构build它们(所以这些用户需要安装Cython)。

我应该如何构build包中的文件来迎合这两种情况?

Cython文档给出了一些指导 。 但是它并没有说明如何使用一个单独的setup.py来处理带/不带Cython的情况。

我现在自己做了一个Python包simplerandom ( BitBucket回购 – 编辑:现在github )(我不指望这是一个stream行的包,但它是一个很好的机会学习Cython)。

此方法依赖于使用Cython.Distutils.build_ext构build.pyx文件(至less在Cython版本0.14中)似乎总是在源.pyx文件所在的目录中创build.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创build的源代码发行版)中:

 ... recursive-include cython * ... 

我没有将mycythonmodule.c提交给版本控制“trunk”(或Mercurial的“默认”)。 当我发布一个版本的时候,我需要记住先做一个python setup.py build_ext ,以确保mycythonmodule.c对于源代码分发是最新的。 我也做一个发布分支,并将C文件提交到分支。 这样我就有了一个与该版本分发的C文件的历史logging。

添加到Craig McQueen的答案:请参阅下面的内容,了解如何覆盖sdist命令,让Cython在创build源代码发布之前自动编译源文件。

这样你的运行没有意外分发过时的C源的风险。 如果您对分销stream程的控制有限,例如在通过持续整合自动创build分销等时,这也有所帮助。

 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 

http://docs.cython.org/src/reference/compilation.html#distributing-cython-modules

强烈build议您分发生成的.c文件以及Cython源文件,以便用户无需安装Cython就可以安装模块。

还build议在您分发的版本中不默认启用Cython编译。 即使用户安装了Cython,他也可能不想仅仅用它来安装你的模块。 此外,他拥有的版本可能与您使用的版本不同,并且可能无法正确编译源代码。

这只是意味着你所提供的setup.py文件只是生成的.c文件中的一个正常的distutils文件,我们将会用这个基本的例子:

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

最简单的是包括但只是使用c文件? 包括.pyx文件很好,但是一旦拥有.c文件就不需要了。 想要重新编译.pyx的人可以安装Pyrex并手动完成。

否则,您需要为构buildC文件的distutils提供一个自定义build_ext命令。 Cython已经包含了一个。 http://docs.cython.org/src/userguide/source_files_and_compilation.html

那些文档没有做的是说如何使这个条件,但是

 try: from Cython.distutils import build_ext except ImportError: from distutils.command import build_ext 

应该处理它。

包括(Cython)生成的.c文件是非常奇怪的。 特别是当我们把它包含在git中的时候。 我宁愿使用setuptools_cython 。 当Cython不可用时,它将构build一个内置Cython环境的蛋,然后使用蛋制作您的代码。

一个可能的例子: https : //github.com/douban/greenify/blob/master/setup.py


更新(2017年1月5日):

由于setuptools 18.0 ,所以不需要使用setuptools_cython 。 这里是一个没有setuptools_cython从头开始构buildCython项目的例子。

这是我编写的一个安装脚本,它使得在构build中包含嵌套的目录变得更容易。 一个需要从包中的文件夹运行它。

Givig这样的结构:

 __init__.py setup.py test.py subdir/ __init__.py anothertest.py 

setup.py

 from setuptools import setup, Extension from Cython.Distutils import build_ext # from os import path ext_names = ( 'test', 'subdir.anothertest', ) cmdclass = {'build_ext': build_ext} # for modules in main dir ext_modules = [ Extension( ext, [ext + ".py"], ) for ext in ext_names if ext.find('.') < 0] # for modules in subdir ONLY ONE LEVEL DOWN!! # modify it if you need more !!! ext_modules += [ Extension( ext, ["/".join(ext.split('.')) + ".py"], ) for ext in ext_names if ext.find('.') > 0] setup( name='name', ext_modules=ext_modules, cmdclass=cmdclass, packages=["base", "base.subdir"], ) # Build -------------------------- # python setup.py build_ext --inplace 

开心编译;)

简单的黑客我想出了:

 from distutils.core import setup try: from Cython.Build import cythonize except ImportError: from pip import pip pip.main(['install', 'cython']) from Cython.Build import cythonize setup(…) 

如果无法导入,请安装Cython。 一个人可能不应该共享这个代码,但是对于我自己的依赖关系来说,这已经足够了。