在 Python w/o 内存泄漏中公开 STL 结构

Exposing STL structs in Python w/o memory leak

我有一个 std::vector<CameraT>,由于 Flexo 对 SWIG and C++ memory leak with vector of pointers 的回复,我已经将其绑定到 Python。但是我需要访问 CameraT.t 之类的字段,但找不到正确的方法。

它在第 3 方头文件 (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" 然后使用另一个 %template 指令,用于 CameraT_ 模板。 (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 headers 中都找不到合适的类型映射。 (尽管在 arrays_java.i 中,Java 确实存在)。有几种修复方法:

  1. 使用 carrays.i 并让用户做一些工作。
  2. 切换到具有现有包装器的容器。 (虽然不是第 3 方代码的选项)
  3. 自己写一些类型映射。

因为 1 和 2 是微不足道的而且不是很好 Python 我将跳过那些并为我们写一两个类型映射,我的最终 nvm.i 文件最终看起来像:

%module nvm
%{
#include "datainterface.h"
%}

%include "std_vector.i"

%typemap(out) float[ANY] %{
  $result = PyTuple_New(_dim0);
  for (unsigned i = 0; i < _dim0; ++i)
    PyTuple_SetItem($result,i,PyFloat_FromDouble([i])); 
%}

%typemap(in) float[ANY] (unsigned i=0, float tmp[_dim0]) %{
   = tmp;
  PyObject *item, *iterator;
  iterator = PyObject_GetIter($input); // spliting this lets a macro work

  while (!PyErr_Occurred() && iterator &&
         i < _dim0 && (item = PyIter_Next(iterator))) {
    [i++] = PyFloat_AsDouble(item);
    Py_DECREF(item);
  }

  Py_DECREF(iterator);

  if (i != _dim0) {
    PyErr_SetString(PyExc_AttributeError, "Failed to get _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>;

这让我 运行 你的测试文件和我的 addtions 没问题:

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)

此解决方案有一个警告,它可以解决(通过 return 使用代理 object)但不会自动破坏交易:

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

这是因为我们 return 编辑了一个新的 Python 元组,其中包含 t 的副本,所以这里的第二个下标运算符指的是那个而不是原始的 C++数据。如果你也想解决这个问题,输出类型映射必须 return 一个实现 __getitem____setitem__ 的代理,而不仅仅是一个元组。根据您希望如何使用代码,需要进行 speed/complexity 权衡。我有 an example of something similar to this,您需要调整输出类型映射以创建 wrapped_array 并调整 wrapped_array 以保存指向实际数组的指针(以及对 Python 的引用object 所以它不会以错误的顺序被释放。