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

问题描述 投票:113回答:9

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

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

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

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

Cython documentation gives a little guidance。但它没有说明如何制作一个单独的setup.py来处理有/无Cython情况。

python packaging cython
9个回答
66
投票

我现在自己做了这个,在一个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的'default')。当我发布时,我需要记住首先执行python setup.py build_ext,以确保mycythonmodule.c存在并且是源代码分发的最新版本。我还创建了一个发布分支,并将C文件提交到分支中。这样我就有了与该版本一起分发的C文件的历史记录。


19
投票

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

18
投票

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"])]
)

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
投票

包括(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是一个在没有setuptools_cython的情况下从头开始构建Cython项目的例子。


2
投票

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

给这样的结构:

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

setup.朋友

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

快乐编译;)


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。一个人可能不应该共享这个代码,但对于我自己的依赖,它就足够了。


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)

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'],
        ),
    ],
)
© www.soinside.com 2019 - 2024. All rights reserved.