仅在setup.py中编译可选的cython扩展

时间:2017-01-21 10:20:27

标签: python cython distutils setup.py distutils2

我在python中完全实现了一个python模块。 (出于便携性原因。)

在cython模块中重复了一小部分的实现。尽可能提高性能。

我知道如何使用.c安装由cython创建的distutils模块。但是,如果一台机器没有安装编译器,我怀疑即使该模块仍然可以在纯python模式下使用,安装程序也会失败。

有可能在可能的情况下编译.c模块,但如果无法进行编译,则可以正常失败并在没有它的情况下安装吗?

3 个答案:

答案 0 :(得分:4)

我想您必须在模块中的setup.py和一个__init__文件中进行一些修改。

假设您的包裹名称为" module"并且你有一个功能sub,你在sub子文件夹中有纯python代码,c_sub子文件夹中有等效的C代码。 例如,在setup.py

import logging
from setuptools.extension import Extension
from setuptools.command.build_ext import build_ext
from distutils.errors import CCompilerError, DistutilsExecError, DistutilsPlatformError

logging.basicConfig()
log = logging.getLogger(__file__)

ext_errors = (CCompilerError, DistutilsExecError, DistutilsPlatformError, IOError)

class BuildFailed(Exception):
    pass

def construct_build_ext(build_ext):
    class WrappedBuildExt(build_ext):
        # This class allows C extension building to fail.
        def run(self):
            try:
                build_ext.run(self)
            except DistutilsPlatformError as x:
                raise BuildFailed(x)

        def build_extension(self, ext):
            try:
                build_ext.build_extension(self, ext)
            except ext_errors as x:
                raise BuildFailed(x)
    return WrappedBuildExt

setup_args = {'name': 'module', 'license': 'BSD', 'author': 'xxx',
    'packages': ['module', 'module.sub', 'module.c_sub'],
    'cmdclass': {}
    }

ext_modules = [Extension("module.c_sub._sub", ["module/c_sub/_sub.c"])]
cmd_classes = setup_args.setdefault('cmdclass', {})

try:
    # try building with c code :
    setup_args['cmdclass']['build_ext'] = construct_build_ext(build_ext)
    setup(ext_modules=ext_modules, **setup_args)
except BuildFailed as ex:
    log.warn(ex)
    log.warn("The C extension could not be compiled")

    ## Retry to install the module without C extensions :
    # Remove any previously defined build_ext command class.
    if 'build_ext' in setup_args['cmdclass']:
        del setup_args['cmdclass']['build_ext']
    if 'build_ext' in cmd_classes:
        del cmd_classes['build_ext']

    # If this new 'setup' call don't fail, the module 
    # will be successfully installed, without the C extension :
    setup(**setup_args)
    log.info("Plain-Python installation succeeded.")

现在,您需要在__init__.py文件中(或在您的案例中的任何相关位置)包含类似内容:

try:
    from .c_sub import *
except ImportError:
    from .sub import *

通过这种方式,如果它是构建版本将使用C版本,否则使用普通python版本。它假定subc_sub将提供相同的API。

您可以在Shapely包中找到example of setup file这样做的方式。实际上我发布的大部分代码都是从这个文件中复制(construct_build_ext函数)或改编(后面的行)。

答案 1 :(得分:1)

Extension在构造函数中具有参数optional

  

可选-指定扩展中的构建失败不应   中止构建过程,而只是跳过扩展。

这也是link提出的一段相当有趣的代码历史的mgc

答案 2 :(得分:0)

问题How should I structure a Python package that contains Cython code

是相关的,那里的问题是如何从Cython回退到“已经生成的C代码”。您可以使用类似的策略来选择要安装的.py.pyx代码。