使用 Cython 和 C++ 组织项目

Posted

技术标签:

【中文标题】使用 Cython 和 C++ 组织项目【英文标题】:Project organization with Cython and C++ 【发布时间】:2013-05-23 11:14:07 【问题描述】:

我想为我的 C++ 项目提供 Python 接口。从技术上讲,我决定使用 Cython 来包装 C++ 代码。随着时间的推移,整个项目旨在成为一个 Python 扩展模块,但起初,这是高度实验性的。逐渐地,C++ 类需要暴露给 Python。

我的问题是如何最好地组织文件和构建配置,以便 Cython 生成的和人工编写的 C++ 代码不会混在一起,并且 Python 扩展模块与其他目标完全分开构建。

我想像这样的源文件目录结构,以及 Cython 的一些构建目录。

Project/
    src/
        *.h
        *.cpp
    cython/
        Project.pyx
        setup.py

【问题讨论】:

你的 C++ 项目可以在没有 Python 的情况下使用吗?创建提供相同 Python 接口的并行 ctypes、基于 cffi 的绑定(以支持更多目标并进行不同权衡)是否有意义? @J.F.Sebastian 该项目可以而且应该在没有 Python 的情况下使用(目前)。我不明白你的第二个问题。 那么从逻辑上讲,您有两个项目:libproject(C++ 库)和 pyproject(允许从 Python 使用 libproject 的 Python 模块)。为了方便(更容易从源代码安装和共同开发),您可以在 pyproject 中包含 libproject。关于第二个问题:基于 ctypes 的绑定可能更容易部署在多个 Python 版本上。基于 cffi 的绑定可用于更好的 Pypy 支持。例如,pyinotify 具有基于 ctypes 和用 C 编写的本机扩展,pyzmq 具有基于 cffi 和 Cython 的实现。是否需要取决于您的项目。 就是这样。在下面的答案中查看有关我如何管理所有内容的更多详细信息。 【参考方案1】:

基本上我有 3 个文件夹:

    CPROJECT,C++ 库:生成 libcproject.so 共享对象 CYPROJECT,cythonized Python 扩展:使用 Cython 生成 cyproject.so DEPENDENCIES,依赖关系:我在其中复制两个项目的外部需求

在 1. 我构建了 C++ 扩展(使用 gcc - -shared-fPIC 编译选项编译)将公开给 python 并且 CYPROJECT 依赖于公开功能到 Python。作为后处理命令,生成的.so 被复制到DEPENDENCIES/libcproject/(以及include 文件中)。这样,库当然也可以在纯 C++ 项目中独立使用。

在 2. 我使用了 3 个子文件夹:

adapters :主要包含 C++ 附加类(通常是从libcproject.so 提供的类派生的类)。这些通常是通过特定于 Cython 要求的功能增强的类(例如存储目标 Python 版本的 PyObject * C 版本 - 继承自给定类的 object - 和引用计数管理,通过 Py_XINCREFPy_DECREF, ...)。 pyext :所有 Cython 手写的 .pyx 文件都存储在哪里。 setup :包含setup.sh 脚本(用于设置依赖路径并调用python setup.py build_ext --inplace 以生成最终的cyproject.so(将添加到PYTHONPATH)和cyproject.pyx

那么setup 子文件夹中有什么?

这是setup.sh 的示例代码:

export PYTHONPATH=$PYTHONPATH:../../../DEPENDENCIES/Cython-0.18
export PATH=$PATH:../../../DEPENDENCIES/libcproject:../../../DEPENDENCIES/Cython-0.18/bin

# Note the `../../../DEPENDENCIES/libcproject`...

CC="gcc"   \
CXX="g++"   \
    python setup.py build_ext --inplace

这里有一个setup.py的例子(主要是为了演示额外的adapters是如何编译的):

import sys
import os
import shutil

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

# Cleaning
for root, dirs, files in os.walk(".", topdown=False):
    for name in files:
        if (name.startswith("cyproject") and not(name.endswith(".pyx"))):
            os.remove(os.path.join(root, name))
    for name in dirs:
        if (name == "build"):
            shutil.rmtree(name)

# Building
setup(
    cmdclass = 'build_ext': build_ext,
    ext_modules = [
    Extension("cyproject", 
              sources=["cyproject.pyx", \
                       "adapter/ALabSimulatorBase.cpp", \
                       "adapter/ALabSimulatorTime.cpp", \
                       "adapter/ALabNetBinding.cpp", \
                       "adapter/AValueArg.cpp", \
                       "adapter/ALabSiteSetsManager.cpp", \
                       "adapter/ALabSite.cpp", \
                       ],
              libraries=["cproject"],
              language="c++",
              extra_compile_args=["-I../inc", "-I../../../DEPENDENCIES/python2.7/inc", "-I../../../DEPENDENCIES/gsl-1.8/include"], 
              extra_link_args=["-L../lib"]
              extra_compile_args=["-fopenmp", "-O3"],
              extra_link_args=[]
              )
    ]
)                   

最后,主要的.pyx,将cython部分的所有手写.pyxs链接在一起[cyproject.pyx]:

include "pyext/Utils.pyx" 
include "pyext/TCLAP.pyx" 
include "pyext/LabSimulatorBase.pyx"
include "pyext/LabBinding.pyx"
include "pyext/LabSimulatorTime.pyx"
...

注意:Cython 生成的所有文件都保留在此 setup 文件夹中,与手写文件(adapterspyext)完全分开,正如预期的那样。

在 3. 使用单独的 DEPENDENCIES 文件夹可以很好地分离(以防我在其他环境中移动 CYPROJECT 及其依赖项)。

所有这些都是为了让您对如何组织此类项目有一个概述(我希望是相关的)。

【讨论】:

这是一个非常具体的例子,但仍然很有帮助。 但我认为这将满足您的需求,因为具体部分是我使用共享 C++ 库(您也应该/应该拥有)和适配器(您迟早会需要) ,我猜)。 好的,随着项目的发展,我会参考你的例子。

以上是关于使用 Cython 和 C++ 组织项目的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Cython 中使用不同的 C++ 编译器?

在 C++ 中嵌入 Cython

如何在 python 包装中使用 unicode 字符串用于带有 cython 的 c++ 类?

如何最好地将 C++/Cython 项目编译成可执行文件?

Cython -std=c++11 错误,同时使用 C 和 C++

在 Cython 中使用 C++ STL 映射