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

时间:2010-12-22 02:44:31

标签: python packaging cython

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

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

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

我应该如何构建包中的文件以满足这两种情况?

Cython documentation gives a little guidance。但它没有说明如何制作一个setup.py来处理有/无Cython案例。

9 个答案:

答案 0 :(得分:60)

我现在已经在Python包中simplerandomBitBucket repo - 编辑:现在github)自己完成了这个(我不认为这是一个受欢迎的包,但是这是学习Cython的好机会。)

此方法依赖于以下事实:使用.pyx(至少使用Cython版本0.14)构建Cython.Distutils.build_ext文件似乎始终在与源相同的目录中创建.c文件.pyx档案。

以下是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提交给版本控制“trunk”(或Mercurial的“默认”)。当我发布版本时,我需要首先记住python setup.py build_ext,以确保mycythonmodule.c存在并且是源代码分发的最新版本。我还创建了一个发布分支,并将C文件提交到分支中。这样我就可以获得随该版本一起发布的C文件的历史记录。

答案 1 :(得分:18)

添加到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

答案 2 :(得分:16)

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

  

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

     

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

     

这只是意味着您附带的setup.py文件将只是生成的.c文件中的正常distutils文件,对于我们将要使用的基本示例:

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

setup(
    ext_modules = [Extension("example", ["example.c"])]
)

答案 3 :(得分:7)

最简单的是包含两者但只使用c文件?包含.pyx文件很不错,但是无论如何都不需要.c文件。想要重新编译.pyx的人可以安装Pyrex并手动完成。

否则,您需要为distutils安装自定义build_ext命令,以便首先构建C文件。 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

应该处理它。

答案 4 :(得分:4)

包含(Cython)生成的.c文件非常奇怪。特别是当我们在git中包含它时。我更喜欢使用setuptools_cython。当Cython不可用时,它将构建一个内置Cython环境的egg,然后使用egg来构建代码。

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


更新(2017年1月5日):

setuptools 18.0以来,无需使用setuptools_cythonHere是一个从头开始构建Cython项目的示例,没有setuptools_cython

答案 5 :(得分:2)

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

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

快乐编译;)

答案 6 :(得分:2)

我想出的简单黑客:

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,请立即安装。一个人可能不应该共享这个代码,但对于我自己的依赖,这已经足够了。

答案 7 :(得分:1)

我发现仅使用setuptools而不是功能受限的distutils的最简单方法是

from setuptools import setup
from setuptools.extension import Extension
try:
    from Cython.Build import cythonize
except ImportError:
    use_cython = False
else:
    use_cython = True

ext_modules = []
if use_cython:
    ext_modules += cythonize('package/cython_module.pyx')
else:
    ext_modules += [Extension('package.cython_module',
                              ['package/cython_modules.c'])]

setup(name='package_name', ext_modules=ext_modules)

答案 8 :(得分:1)

所有其他答案都依赖

  • distutils
  • Cython.Build导入,这会在通过setup_requires要求cython并将其导入之间造成鸡到蛋的问题。

一种现代的解决方案是改为使用setuptools,请参见this answer(自动处理Cython扩展程序需要setuptools 18.0,即它已经可用了很多年)。具有需求处理,入口点和cython模块的现代标准setup.py看起来像这样:

from setuptools import setup, Extension

with open('requirements.txt') as f:
    requirements = f.read().splitlines()

setup(
    name='MyPackage',
    install_requires=requirements,
    setup_requires=[
        'setuptools>=18.0',  # automatically handles Cython extensions
        'cython>=0.28.4',
    ],
    entry_points={
        'console_scripts': [
            'mymain = mypackage.main:main',
        ],
    },
    ext_modules=[
        Extension(
            'mypackage.my_cython_module',
            sources=['mypackage/my_cython_module.pyx'],
        ),
    ],
)