在 Python 中公开 STL 结构,不带内存泄漏

Exposing STL structs in Python w/o memory leak

本文关键字:内存 泄漏 结构 STL Python      更新时间:2023-10-16

我有一个std::vector<CameraT>,我已经绑定到Python,这要归功于Flexo对SWIG的响应和C++指针向量的内存泄漏。但是我需要访问像 CameraT.t 这样的字段,但我找不到合适的方法来做到这一点。

它在第三方头文件 (DataInterface.h) 中定义为:

template<class FT>
struct CameraT_ {
    typedef FT float_t;
    float_t t[3];
    /* more elaborate c++ stuff */
};
typedef CameraT_<float> CameraT;

界面文件大致如下所示:

%module nvm
%{
    #define SWIG_FILE_WITH_INIT
    #include "util.h"
%}
%include "std_vector.i"
%inline %{
bool CameraDataFromNVM(const char* name, std::vector<CameraT>& camera_data){
    /* wrapper code */
}
%}
%template(CamVector) std::vector<CameraT>;

和集成测试:

import nvm
if __name__ == "__main__":
    datafile = '../../example-data/vidstills.nvm'
    cams = nvm.CamVector()
    assert nvm.CameraDataFromNVM(datafile, cams)
    print(cams[0])  # this yields no memory warnings
    print(cams[0].t[0])  # 'SwigPyObject' object has no attribute 't'
    # Yields: swig/python detected a memory leak of type 'CameraT *', no destructor found.
    c = list([i for i in cams])

打印此内容:

$ python3 test_nvm.py |& head
<Swig Object of type 'CameraT *' at 0x7f638228ee40>
Loading cameras/points: ../../example-data/vidstills.nvm
329 cameras; 8714 3D points; 74316 projections
Traceback (most recent call last):
  File "test_nvm.py", line 20, in <module>
    print(cams[0].t[0])
AttributeError: 'SwigPyObject' object has no attribute 't'
swig/python detected a memory leak of type 'CameraT *', no destructor found.

我试图在 %inline 块内外放置struct CameraT {}#include语句,但失败了。循环访问矢量元素也会产生内存泄漏警告。不知道还能做什么。

代码在Github上。

这里有几个问题。首先,要修复有关内存泄漏的警告,您需要%include "datainterface.h",然后对CameraT_模板使用另一个 %template 指令。(typedef 不会更改这是必需的事实)。

所以有了:

%include "datainterface.h"
%template(CameraT) CameraT_<float>;

警告消失,我们可以访问该类型的成员。(我认为,您提到的示例第一行缺少警告是 Python 引用计数系统的一个怪癖)。

这还不是全部,我们现在收到一个关于t不可下标的错误。我们可以通过使用 -debug-tmsearch 调用 swig 作为额外的参数来深入了解这一点,该参数显示了正在选择的类型图。至关重要的是,我们看到类似的东西:

datainterface.h:4: Searching for a suitable 'ret' typemap for: CameraT_< float >::float_t CameraT_< float >::t[3]
[snip lots of tries...]
Looking for: SWIGTYPE
None found

有点令人惊讶的是,我在任何 SWIG 标头中都找不到任何合适的类型图。(它确实存在于Java中,尽管在arrays_java.i中)。有几种方法可以修复它:

  1. 使用 carrays.i 并让用户做一些工作。
  2. 切换到包含现有包装器的容器。(虽然不是第三方代码的选项)
  3. 我们自己写一些打字图。

由于 1 和 2 是微不足道的,不是很好的 Python,我将跳过它们并为我们编写一两个类型图,我的最终 nvm.i 文件最终看起来像:

%module nvm
%{
#include "datainterface.h"
%}
%include "std_vector.i"
%typemap(out) float[ANY] %{
  $result = PyTuple_New($1_dim0);
  for (unsigned i = 0; i < $1_dim0; ++i)
    PyTuple_SetItem($result,i,PyFloat_FromDouble($1[i])); 
%}
%typemap(in) float[ANY] (unsigned i=0, float tmp[$1_dim0]) %{
  $1 = tmp;
  PyObject *item, *iterator;
  iterator = PyObject_GetIter($input); // spliting this lets a macro work
  while (!PyErr_Occurred() && iterator &&
         i < $1_dim0 && (item = PyIter_Next(iterator))) {
    $1[i++] = PyFloat_AsDouble(item);
    Py_DECREF(item);
  }
  Py_DECREF(iterator);
  if (i != $1_dim0) {
    PyErr_SetString(PyExc_AttributeError, "Failed to get $1_dim0 floats" );
    SWIG_fail;
  }
%}
%include "datainterface.h"
%inline %{
bool CameraDataFromNVM(const char* name, std::vector<CameraT>& camera_data){
  return true;
}
%}
%template(CameraT) CameraT_<float>;
%template(CamVector) std::vector<CameraT>;

这让我可以使用我的添加很好地运行您的测试文件:

import nvm
if __name__ == "__main__":
    datafile = '../../example-data/vidstills.nvm'
    cams = nvm.CamVector(1)
    assert nvm.CameraDataFromNVM(datafile, cams)
    print(cams[0])  # this yields no memory warnings
    cams[0].t = (1,2,3)
    print(cams[0].t[0])
    #print(cams[0].bar)
    # Yields: swig/python detected a memory leak of type 'CameraT *', no destructor found.
    c = list([i for i in cams])
    print(c)

此解决方案有一个警告,它是可以解决的(通过返回代理对象),但不会自动成为交易破坏者:

cams[0].t[0] = 1 
print(cams[0].t[0]) # Won't actually have changed

发生这种情况是因为我们返回了一个新的 Python 元组,其中包含t的副本,因此此处的第二个下标运算符引用该元组而不是原始C++数据。如果你也想解决这个问题,out类型映射必须返回一个实现__getitem____setitem__的代理,而不仅仅是一个元组。需要权衡速度/复杂性,具体取决于您希望如何使用代码。我有一个类似的例子,你需要调整out类型图来创建一个wrapped_array,并调整wrapped_array以持有指向真实数组的指针(以及对Python对象的引用,这样它就不会以错误的顺序获得自由)。