1

我有一个项目,我在其中混合 cpp 和 python 代码。

由于多种原因,前端需要在 python 中,后端需要在 cpp 中。

现在,我正在寻找关于如何将我的 python 对象传递给 cpp 的解决方案。需要注意的一个事实是,后端需要在某个时候回调到 python 中以计算一些数字,其中 python 函数将返回一个浮点数列表。

我一直在查看此处定义的 pybind 类型转换选项: https ://pybind11.readthedocs.io/en/stable/advanced/cast/index.html

但是,对我来说,选项 1 似乎很容易使用,我可以在这里看到: https ://pybind11.readthedocs.io/en/stable/advanced/classes.html#overriding-virtual-functions-in-python

所以我想知道,为什么有人会选择3号?它与选项 1 相比如何?

非常感谢

4

1 回答 1

1

是的,如果主要代码是 C++ 并且绑定很好,那么选项 1 是最容易使用的,因为在这种情况下,绑定的 C++ 对象在 Python 中与原生 Python 类一样自然。它使生活更轻松,因为您可以完全控制对象身份以及是否复制。

对于 3,我发现 pybind11 在使用回调时复制过于激进(这似乎是您的用例),例如,对于 numpy 数组,如果验证它是连续的,则完全有可能在 C++ 端使用缓冲区. 当然,复制可以防止内存问题,但是对复制与非复制的控制太少了(numpy 有同样的问题 tbs)。

3 存在的原因主要是因为它提高了可用性并提供了很好的语法。例如,如果我们有一个带有这个签名的函数:

void func(const std::vector<int>&)

那么很高兴能够从 Python 端调用它 asfunc((1, 2, 3))甚至func(range(3)). 它方便,易于使用,看起来很干净等。但是到那时,除了复制之外别无他法,因为 a 的内存布局与 atuple如此不同std::vector(并且范围甚至不代表内存中的容器)。

但是请注意,在上面的func示例中,调用者仍然可以决定提供绑定std::vector<int>对象,从而抢占任何复制。可能看起来不太好,但可以完全控制。这很有用,例如,如果向量是某个其他函数的返回值,或者在调用之间被修改:

v = some_calc()   # with v a bound C++ vector
func(v)
v.append(4)       # add an element
func(v)

将此与计算一些数字后返回浮点列表的情况进行对比,类似于(但不完全是)您的描述:

std::list<float> calc()

如果选择“选项 1”,则绑定函数calc将返回绑定的 C++ 对象std::list<float>。如果您选择“选项 3”,则绑定函数calc将返回一个 Python ,其中包含复制到其中list的 C++ 内容。std::list<float>

“选项 3”出现的问题是,如果调用者实际上想要一个绑定的 C++ 对象,那么需要将这些值复制回一个新列表中,因此总共需要 2 个副本。OTOH,如果您选择“选项 1”并且调用者想要一个 Python list,那么如果需要,他们可以自由地对返回值进行复制calc

res = calc()
list_res = list(res)

甚至,如果他们一直想要这个:

def pycalc():
    return list(calc())

现在终于到了你的具体情况,它是一个从 C++ 调用的 Python 回调,它返回一个浮点数列表。如果您使用“选项 1”,则 Python 函数将被迫创建一个 C++ 列表以返回,例如(使用 typecpplist赋予绑定类型的名称std::list<float>):

def pycalc():
    return cpplist(range(3))

Python 程序员不会觉得这很漂亮。相反,通过选择“选项 3”,检查返回类型并在需要时进行转换,这也是有效的:

def pycalc():
    return [x for x in range(3)]

然后,根据总体要求和典型用例,您的用户可能会更喜欢“选项 3”。

于 2020-02-02T03:23:29.207 回答