【问题标题】:cython reuse wrapped C++ classcython 重用包装的 C++ 类
【发布时间】:2021-04-27 15:53:51
【问题描述】:

我想用 cython 包装一些 C++ 类,但我的问题是我必须在另一个类中使用一些类。我没有找到其他人有同样的问题,甚至没有类似的问题,如果已经被问到,我很抱歉......我做了一个已经很长的简约例子......

所以我有以下文件结构,BuildAll.sh 在文件夹中运行,如果它有一个 BuildAll.sh 文件,它会更深入,如果一个文件夹有一个 setup.py 文件,它会构建它(setup.py 文件将查找每个 *.pyx 文件并构建它们) 现在出于虚拟目的,classAC 是用 C++ 编写的 ClassA,它拥有一个 int 作为成员。 B 文件夹中也是如此,但 classBC 将 classAC 的实例作为成员,我认为这是问题所在,因为 classBC 不知道 classAC 的定义。

.
├── BuildAll.sh
├── main.py
└── src
    ├── A
    │   ├── classAC.cpp
    │   ├── classAC.hpp
    │   ├── classAC.pxd
    │   ├── classA.pyx
    │   ├── __init__.py
    │   └── setup.py
    ├── B
    │   ├── classBC.cpp
    │   ├── classBC.hpp
    │   ├── classBC.pxd
    │   ├── classB.pyx
    │   ├── __init__.py
    │   └── setup.py
    ├── BuildAll.sh
    └── __init__.py

BuildAll.sh:

#!/bin/bash

# Go into every directory
for D in */
do
    cd $D
    # If dir contains a BuildAll.sh script, run it.
    if test -f "BuildAll.sh"; then 
        ./BuildAll.sh
    fi
    # If dir contains a setup.py file, then build it.
    # setup.py will look for every .pyx extension and build it automatically.
    if test -f "setup.py"; then
        python3 setup.py build_ext --inplace
    fi
    cd ..
done

setup.py:

from setuptools import Extension, setup
from Cython.Build import cythonize
import glob

PYXFILES = glob.glob("*.pyx")
EXTNAMES = [i[:-4] for i in PYXFILES]

ext_list = []
for i in range(len(PYXFILES)):
    ext_list.append(
        Extension(
            EXTNAMES[i],
            [PYXFILES[i]],
            extra_compile_args=["-O3"]
        )
    )

setup(
    ext_modules = cythonize(
        ext_list,
        language_level = 3,
        build_dir = 'build',
        annotate = True
    )
)

classAC.cpp:

#include "classAC.hpp"

classAC::classAC()
    : data_(0)
{}

int classAC::data()
{
    return data_;
}

classAC.hpp:

#ifndef CLASSAC_H
#define CLASSAC_H

class classAC
{
private:
    int data_;
public:
    // Constructors
    // Null construct
    classAC();

    // Destructor
    ~classAC() = default;

    int data();
};

#endif // CLASSAC_H

classAC.pxd:

cdef extern from "classAC.cpp":
    pass

cdef extern from "classAC.hpp":
    cdef cppclass classAC:
        classAC()
        int data()

classA.pyx:

# distutils: language = c++

from classAC cimport *

cdef class ClassA:

    cdef classAC COBJ
    def __cinit__(self):
        pass

    def __init__(self):
        self.COBJ = classAC()

    def getAdata(self):
        return self.COBJ.data()

classBC.cpp:

#include "classBC.hpp"

classBC::classBC()
    : aobj_(classAC())
{}

classBC.hpp:

#ifndef CLASSBC_H
#define CLASSBC_H

#include "classAC.hpp"

class classBC
{
    /* Base class for an fvMesh */
private:
    classAC aobj_;
public:
    // Constructors
    classBC();

    // Destructor
    ~classBC() = default;
};

#endif // CLASSBC_H

classBC.pxd:

cdef extern from "classBC.cpp":
    pass

cdef extern from "classBC.hpp":
    cdef cppclass classBC:
        classBC()

classB.pyx:

# distutils: language = c++
# distutils: include_dirs = ../A

from classBC cimport *

cdef class ClassB:
    """
    """
    cdef classBC COBJ
    def __cinit__(self):
        pass

    def __init__(self):
        self.COBJ = classBC()

如果我尝试运行我的 main.py:

#!/usr/bin/python3

from src.A.classA import ClassA
from src.B.classB import ClassB

a = ClassA()
print(a.getAdata())

b = ClassB()

我收到以下错误:

Traceback (most recent call last):
  File "./main.py", line 4, in <module>
    from src.B.classB import ClassB
ImportError: <pathToThisFolder>/src/B/classB.cpython-38-x86_64-linux-gnu.so: undefined symbol: _ZN7classACC1Ev

我尝试将 classA.longname.so 文件链接到 classBC,但没有帮助。
甚至有可能实现这个功能吗?如果是,怎么做?
我想将所有内容分开,而不是拥有一个巨大的模块。
我的目标是让每个 setup.py 文件保持原样,并且只在 pyx 文件中使用 #distutils 添加特定的扩展选项。

谢谢你的帮助,如果这个结构真的很糟糕,请告诉我......

【问题讨论】:

  • 我知道我可以用 cython 编写(而不是从 cpp 文件构建),但我做了一些基准测试,如果我用 c++ 编写代码并包装它,它会比我运行它的成员函数更快在 cython 中编写相同的函数。

标签: python c++ class cython


【解决方案1】:

您通常会在 setup.py 中使用“sources”选项,或者在 .pyx 文件中使用 # distutils: sources = filename.cpp, another_filename.cpp 指令。这记录在https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html#configuring-the-c-build 中。您这样做而不是将 cpp 文件逐字包含到您的 pxd 文件中!

因此您的classB.pyx 包含该行

# distutils: sources = classBC.cpp classAC.cpp

因此它们都将被编译并链接到扩展模块中。

distutils 指令方法可能比将其放在 setup.py 中更好,因为依赖项记录在使用它们的位置。


另一种方法(基于 cmets 可能对您更好)是将所有 C++ 代码链接到一个动态库中,然后将每个单独的 Cython 模块与该库链接(使用 distutils: libraries = ...。再次,您只是在告诉您在 Cython 代码中包含头文件(而不是 .cpp 文件)。

【讨论】:

  • 谢谢,但是这样classA也会被编译成classA和classBc,对吧?但是是否可以将编译的classA链接到classB?还是每次使用这个类都必须重新编译?
  • Cython 模块相互独立。但是,如果您更改 C++ classAC,则需要重新编译 Cython 模块 classB。我不会将 Cython 模块相互链接。另一种选择是将 C++ 代码编译为单独的库(静态或动态),将每个 Cython 模块链接到该库(并且不要将 cpp 文件编译到 Cython 模块中)
  • 所以如果我理解正确的话,只是为了澄清。如果我更进一步并添加使用 B 的类 C,那么在 C 中我必须包含 A 和 B 的源代码,如果我创建使用 C 的类 D,那么我必须包含 A 的源代码, B、C?像这样它会很快炸毁。因此,在这种情况下,如果我从 A、B、C 构建动态库并在链接到该库的单独 cython 模块中创建接口会更好?如果我以这种方式正确,则被包装的 C 将了解 A 和 B 的所有信息,但只需链接一个库。
  • 是的,听起来您理解正确。如果您打算拥有很多模块,那么动态库绝对是您的最佳选择
  • 是的。非常感谢您的帮助!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多