将多个 C++ 类包装成一个 Python 类

Posted

技术标签:

【中文标题】将多个 C++ 类包装成一个 Python 类【英文标题】:Wrapping multiple C++ classes into one Python class 【发布时间】:2017-06-28 16:43:33 【问题描述】:

Cython 允许通过包装函数和类来使用 C 和 C++ 库。我想使用的两个库是 SFML 和 Box2D。它们定义了单独的 Vector2 类。是否可以为这两个库(可能更多)编写一个 Vector2 Python 包装器?我的目标是在与不同库交互时使用单一 Python 类型的 Vector2。

有人已经做过类似的事情了吗?这将简化事情并统一我的 Python API。我已经封装了部分 SFML,但除此之外我是 Cython 的新手。

对于参考SFML's Vector2 看起来像这样:

template <typename T> class Vector2 
    public:
        T x;
        T y;
        Vector2();
        Vector2(T X, T Y);
        template <typename U> explicit Vector2(const Vector2<U>& vector);
        ...
;

还有Box2D vector:

struct b2Vec2 
    float32 x, y;
    b2Vec2() 
    b2Vec2(float32 xIn, float32 yIn);
    ...
;

【问题讨论】:

您可以轻松地将两个类都存在于同一个类中,但是,快速浏览一下 SFML 的 Vector2 实现会发现无法访问 data() 成员,这意味着可能必须复制任何数据结束(这不是numpy.ctypeslib.as_array 友好)。你可以很容易地为这两者提供一个包装器,只需要一点语法糖,但你会考虑为每个人复制数据以使用每个单独的库中的方法。 另一个(非常简单的选项)是您可以分别导出两个类,然后有一个包装器来执行共享的方法子集(如乘法、除法等),存储两个导出的类之一作为数据成员。如果你愿意,我可以举个例子作为答案。 @AlexanderHuszagh 很高兴看到一个例子。 从设计和维护的角度来看,最好每 1 个 C++ 类有 1 个 Python 类。您可以使用第 3 个 Python 类来包装这 2 个。 Python 中通常的做法是重要的是接口而不是类,因此如果两个类具有相同的接口,那么它们应该可以互换使用 【参考方案1】:

概述

大体思路是三个步骤:

    为您的类型编写声明 使用相同的接口单独包装每个类 编写一个广义向量包装器

声明

首先,您需要一个用于包装类型的 Cython 声明。一个例子(非常少)如下。

# This is the Cython interface for Vector2

cdef float float32 

cdef extern from "Vector2.hpp" nogil:
    # if this was an stl vector, use vector[T, ALLOCATOR=*]
    # you must list every template type, even one with default values
    cdef cppclass Vector2[T]:   
        ctypedef T value_type

        # Add all the overloads, for example, multiplication
        Vector2[T, ALLOCATOR] operator*(const T&) const;


cdef extern from "b2Math.h" nogil:
    cdef cppclass b2Vec2:

        # Add all overloads, for example, multiplication
        void operator*=(float)

界面

接下来,您需要将它们包装到 Python 包装器中。您需要确保它们具有相同的接口,以便可以像使用另一个一样使用它们。 Python data model 是您的朋友。一个非常简单的例子是:

cdef class SfmlVector2_32:
    # This is a definition for a float32 vector from Sfml
    Vector2[float32] c

    def __mul__(self, float32 x):
        # multiple and assign
        B2Vector2_32 copy;
        copy.c = c * x
        return copy;

cdef class B2Vector2_32:
    # This is a definition for a float32 vector from box2
    b2Vec2 c

    def __mul__(self, float32 x):
        # multiple and assign
        B2Vector2_32 copy;
        copy.c = c;
        copy.c *= x
        return copy

广义向量

最后,您需要一个简单的包装类,它绑定多种向量类型中的一种,并对类执行正确的操作。由于 Python 不是静态类型的,而且您包装的每个向量的接口都是相同的,因此这很容易:只需在 __init__ 中初始化正确的向量。

class Vector2_32:

    def __init__(self, framework = 'SFML'):
        # ideally, perform a dictionary lookup if you have many classes
        # O(1) for dict, if/then is O(n)
        if framework == 'SFML':
            self.vector = SfmlVector2_32()
        elif framework == 'BOX2':
            self.vector = B2Vector2_32()
        else:
            raise TypeError("Unrecognized framework.")

    def __mul__(self, float32 x):
        Vector2_32 copy
        copy.vector = vector * x
        return copy

想法

这是一个非常糟糕的示例,因为通常您根据__imul__(就地乘法)来实现__mul__(乘法),但它可以理解总体思路。这段代码可能有错别字,我现在没有机器来编译它。

【讨论】:

很好的答案。我有一个问题:ALLOCATOR=* 是做什么的? @PythonFanboy STL 向量定义为std::vector&lt;T, std::allocator&lt;T&gt;,表示它需要两种类型,而不是1。Cython 需要知道这一点,但它不能使用第二种类型。这会通知 Cython vector 采用两种类型作为模板,但第二种是默认初始化的。在这种情况下,我错了,Sfml 不允许您定义分配器。我将编辑我的答案。 您不必告诉 Cython 第二种类型。如果文本 std::vector&lt;T&gt; 生成有效的 C++ 代码并适当替换 T(它确实如此),那么 Cython 真的不需要知道第二个参数。

以上是关于将多个 C++ 类包装成一个 Python 类的主要内容,如果未能解决你的问题,请参考以下文章

为python(使用opencv)包装c ++类,给出错误

Boost Python 包装的虚拟类子返回错误:与 C++ 签名不匹配

SWIG 在 Windows 中生成 C++ Python3 包装器导致断言 MSVC 2017

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

C++ 句柄类

将 python 函数传递给 SWIG 包装的 C++ 代码