130

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

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

但是对于可能想要修改包的人,我还想提供 Cython.pyx文件,并且以某种方式还允许setup.py使用 Cython 构建它们(因此这些用户需要安装 Cython)。

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

Cython文档提供了一些指导。但它并没有说明如何制作一个setup.py同时处理有/没有 Cython 情况的单曲。

4

10 回答 10

78

我现在自己在 Python 包中完成了此操作simplerandomBitBucket repo - 编辑:现在的github)(我不认为这是一个流行的包,但这是学习 Cython 的好机会)。

这种方法依赖于这样一个事实,即使用(至少使用 Cython 版本 0.14)构建.pyx文件似乎总是在与源文件相同的目录中创建文件。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版本控制“主干”(或 Mercurial 的“默认”)。当我发布一个版本时,我需要记住首先做一个python setup.py build_ext,以确保mycythonmodule.c源代码分发存在并且是最新的。我还创建了一个发布分支,并将 C 文件提交到该分支。这样我就有了随该版本分发的 C 文件的历史记录。

于 2010-12-23T01:58:32.627 回答
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
于 2013-08-24T12:24:18.327 回答
21

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"])]
)
于 2013-10-02T13:28:24.567 回答
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

应该处理。

于 2010-12-22T07:30:01.000 回答
4

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

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


更新(2017-01-05):

因为setuptools 18.0,没有必要使用setuptools_cython. 是一个从头开始构建 Cython 项目的示例,没有setuptools_cython.

于 2014-12-11T10:23:56.687 回答
3

我想出的简单技巧:

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。可能不应该共享此代码,但对于我自己的依赖项来说,它已经足够好了。

于 2016-05-18T01:01:17.950 回答
3

所有其他答案要么依赖

  • distutils
  • 从 导入Cython.Build,这在需要 cython viasetup_requires和导入它之间产生了鸡与蛋的问题。

现代解决方案是改用 setuptools,请参阅此答案(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'],
        ),
    ],
)
于 2018-07-30T11:29:46.403 回答
2

这是我编写的设置脚本,它可以更轻松地在构建中包含嵌套目录。需要从包中的文件夹运行它。

Givig 结构如下:

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

安装程序.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

编译愉快;)

于 2014-06-24T13:33:11.097 回答
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)
于 2018-01-26T13:48:28.397 回答
0

build_ext我想我通过提供自定义命令找到了一种很好的方法。思路如下:

  1. 我通过在函数体中覆盖finalize_options()和执行来添加 numpy 标头,这很好地避免了 numpy 在安装import numpy之前不可用的问题。setup()

  2. 如果 cython 在系统上可用,它会挂接到命令的check_extensions_list()方法中,并通过对所有过时的 cython 模块进行 cython 化,将它们替换为以后可以由该build_extension() 方法处理的 C 扩展。我们也只是在我们的模块中提供了功能的后半部分:这意味着如果 cython 不可用但我们有 C 扩展,它仍然可以工作,这允许您进行源代码分发。

这是代码:

import re, sys, os.path
from distutils import dep_util, log
from setuptools.command.build_ext import build_ext

try:
    import Cython.Build
    HAVE_CYTHON = True
except ImportError:
    HAVE_CYTHON = False

class BuildExtWithNumpy(build_ext):
    def check_cython(self, ext):
        c_sources = []
        for fname in ext.sources:
            cname, matches = re.subn(r"(?i)\.pyx$", ".c", fname, 1)
            c_sources.append(cname)
            if matches and dep_util.newer(fname, cname):
                if HAVE_CYTHON:
                    return ext
                raise RuntimeError("Cython and C module unavailable")
        ext.sources = c_sources
        return ext

    def check_extensions_list(self, extensions):
        extensions = [self.check_cython(ext) for ext in extensions]
        return build_ext.check_extensions_list(self, extensions)

    def finalize_options(self):
        import numpy as np
        build_ext.finalize_options(self)
        self.include_dirs.append(np.get_include())

这允许人们只写setup()参数而不用担心导入以及是否有可用的 cython:

setup(
    # ...
    ext_modules=[Extension("_my_fast_thing", ["src/_my_fast_thing.pyx"])],
    setup_requires=['numpy'],
    cmdclass={'build_ext': BuildExtWithNumpy}
    )
于 2020-01-18T18:19:15.887 回答