将多个 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<T, std::allocator<T>
,表示它需要两种类型,而不是1。Cython 需要知道这一点,但它不能使用第二种类型。这会通知 Cython vector
采用两种类型作为模板,但第二种是默认初始化的。在这种情况下,我错了,Sfml 不允许您定义分配器。我将编辑我的答案。
您不必告诉 Cython 第二种类型。如果文本 std::vector<T>
生成有效的 C++ 代码并适当替换 T(它确实如此),那么 Cython 真的不需要知道第二个参数。以上是关于将多个 C++ 类包装成一个 Python 类的主要内容,如果未能解决你的问题,请参考以下文章
Boost Python 包装的虚拟类子返回错误:与 C++ 签名不匹配
SWIG 在 Windows 中生成 C++ Python3 包装器导致断言 MSVC 2017