【问题标题】:Distributing a shared library and some C code with a Cython extension module使用 Cython 扩展模块分发共享库和一些 C 代码
【发布时间】:2012-06-16 03:09:38
【问题描述】:

我正在尝试从大型 C++ 共享库 (libbig.so) 中获取一些函数,并通过 Cython 将它们公开给 Python。为此,我有一个小的 C++ 文件 (small.cpp),它为我需要的共享库中的功能提供了一个精简的包装器,以一种易于通过 Cython (pysmall.pyx) 调用的方式。

libbig.so -> small.cpp, small.h -> libsmall.so -> pysmall.pyx -> pysmall.cpp -> pysmall.so

我可以在自己的计算机上构建和运行这个扩展模块:我只是将 small.cpp 编译成 libsmall.so,然后在 setup.py 的 Extension 对象中说“libraries=['small']”来构建扩展模块 pysmall.so.

我现在正在尝试分发此扩展模块,并且很难找到描述 setup.py 分发 Cython 模块以及 C 源代码和共享库的最佳实践的资源。我读过“Installing Python Modules”、“Distributing Python Modules”和“Distributing Cython Modules”。我了解如何自行分发扩展模块。我不太确定分发扩展模块依赖项的最佳方式。

Cython 文档表明您应该包含生成的 .cpp 文件以及 .pyx 文件,以防 Cython 不存在,但它没有提供代码来演示如何最好地处理每种情况。它也没有提到如何分发 Cython 模块所依赖的共享库。

我正在研究来自 pandas、lxml、pyzmq、h5py 等的 setup.py 脚本,并且发生了很多无关的工作。如果有人有可能加速这个过程的指针或示例代码,我当然会很感激!

【问题讨论】:

    标签: python cython distutils


    【解决方案1】:

    1) 分发 libbig.so

    这是 python 无法帮助您解决的问题。你的目标是谁?如果是 linux,你能要求他们用他们的包管理器安装它吗?如果 libbig 不是通过包管理器分发的,或者它不是 linux 并且您的目标是多个架构,则可能必须分发 libbig 源代码。

    2) Cython/setuptools。

    坦率地说,我认为只要求人们拥有 Cython 是最简单的。这样,代码只有一个基本事实版本,您不必担心 .pyx.cpp 代码之间的不一致。最简单的方法是使用setuptools 而不是distutils。这样,您可以使用:

    setup('mypackage',
        ...
        install_requires=['cython'])
    

    总的来说,您的 setup.py 脚​​本将类似于:

    # setup.py
    
    from setuptools import setup, Extension
    from Cython.Distutils import build_ext
    
    pysmall = Extension('pysmall',
        sources = ['pysmall.pyx', 'small.cpp'],
        include_dirs = ['include/'])
    
    setup(name='mypackage',
          packages=['yourpurepythonpackage'],
          install_requires=['cython==0.17'],
          ext_modules=[pysmall],
          cmdclass = {'build_ext': build_ext})
    

    如果您不喜欢需要 cython 的想法,您可以执行以下操作:

    # setup.py
    
    import warnings
    try:
        from Cython.Distutils import build_ext
        from setuptools import setup, Extension
        HAVE_CYTHON = True
    except ImportError as e:
        HAVE_CYTHON = False
        warnings.warn(e.message)
        from distutils.core import setup, Extension
        from distutils.command import build_ext
    
    pysmall = Extension('pysmall',
        sources = ['pysmall.pyx', 'small.cpp'],
        include_dirs = ['include/'])
    
    configuration = {'name': 'mypackage',
          'packages': ['yourpurepythonpackage'],
          'install_requires': ['cython==0.17'],
          'ext_modules': [pysmall],
          'cmdclass': {'build_ext': build_ext}}
    
    if not HAVE_CYTHON:
        pysmall.sources[0] = 'pysmall.cpp'
        configuration.pop('install_requires')
    
    setup(**configuration)
    

    【讨论】:

    • 请注意,在较新的setuptoolsdistutils 版本中(我使用的是setuptools 5.7),这些命令被移到了它们自己的模块中。所以你会想分别做from setuptools.command.build_ext import build_extdistutils
    • 您的第一个 setup.py 是在 Cython.Distutils 不存在的情况下在它有机会安装之前导入它。
    • 另一种选择是创建一个 conda 包,可以捆绑libbig.soconda.io/docs/user-guide/tutorials/build-postgis.html
    【解决方案2】:

    这是我的棘手解决方案。这个想法是“隐藏”cython 的存在,直到它被要求安装。这可以通过惰性评估来实现。这是一个例子:

    from setuptools import setup, Extension
    
    class lazy_cythonize(list):
        def __init__(self, callback):
            self._list, self.callback = None, callback
        def c_list(self):
            if self._list is None: self._list = self.callback()
            return self._list
        def __iter__(self):
            for e in self.c_list(): yield e
        def __getitem__(self, ii): return self.c_list()[ii]
        def __len__(self): return len(self.c_list())
    
    def extensions():
        from Cython.Build import cythonize
        ext = Extension('native_ext_name', ['your/src/*.pyx'])
        return cythonize([ext])
    
    
    configuration = {
        'name': 'mypackage',
        'packages': ['yourpurepythonpackage'],
        'install_requires': ['cython==0.17'],
        'ext_modules': lazy_cythonize(extensions)
    }
    
    setup(**configuration)
    

    lazy_cythonize 是一个虚假列表,仅当有人尝试访问它时才会生成其内部元素。
    需要时,此类导入Cython.Build 并生成扩展列表。这样可以避免在项目中保留 *.c 文件,从而在构建模块时需要安装 cython。

    相当棘手,但实际上它正在工作。

    【讨论】:

    • 很酷的模式,这也适用于在 setup_requires 设置运行时需要 cython 的 setuptools :)
    • 这正是我想要的。当 Cython 在脚本顶部导入时,感觉需要安装 Cython。
    【解决方案3】:

    我已经为 setuptools 288 推送了一个修复程序,计划作为 setuptools 18.0 发布。 This changelog entry 描述了一种适用于该构建的技术。 beta release 可用于测试。

    【讨论】:

    • 如果用户有旧版本的 setuptools 怎么办?
    • @RichardHansen 你有几个选择。您可以要求您的用户升级(今天可能为时过早),捆绑 ez_setup.py 并在安装软件包期间强制 setuptools 升级(不推荐),或者检测 setuptools 版本并回退到不太优雅的方法。
    • 仍然必须在 setuptools 中使用惰性列表模式的解决方法>=18 for setup_requires deps (!= cython) @JasonR.Coombs
    • @marscher:这听起来对我来说是正确的。 Cython 是特殊情况的原因是因为在 setuptools 中支持 Cython,并且因为 Cython 是一个构建工具。我可以看到通用钩子的优点,它允许在调用 setup() 之前但在解决 setup_requires 之后注入行为。
    猜你喜欢
    • 1970-01-01
    • 2022-10-25
    • 1970-01-01
    • 2021-11-25
    • 2015-01-09
    • 1970-01-01
    • 1970-01-01
    • 2011-03-16
    • 1970-01-01
    相关资源
    最近更新 更多