是的,如果主要代码是 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”。