如何在不复制对象的情况下公开将 C++ 对象返回给 Python 的函数?
How to expose a function returning a C++ object to Python without copying the object?
在 中,我学习了如何通过复制对象将 returning C++ 对象的函数公开给 Python。必须执行复制似乎不是最佳选择。我如何 return 对象而不复制它?即我如何直接访问 PyPeakDetection.getPeaks
中由 self.thisptr.getPeaks(data)
编辑的峰值 return(在 peak_detection_.pyx 中定义)?
peak_detection.hpp
#ifndef PEAKDETECTION_H
#define PEAKDETECTION_H
#include <string>
#include <map>
#include <vector>
#include "peak.hpp"
class PeakDetection
{
public:
PeakDetection(std::map<std::string, std::string> config);
std::vector<Peak> getPeaks(std::vector<float> &data);
private:
float _threshold;
};
#endif
peak_detection.cpp
#include <iostream>
#include <string>
#include "peak.hpp"
#include "peak_detection.hpp"
using namespace std;
PeakDetection::PeakDetection(map<string, string> config)
{
_threshold = stof(config["_threshold"]);
}
vector<Peak> PeakDetection::getPeaks(vector<float> &data){
Peak peak1 = Peak(10,1);
Peak peak2 = Peak(20,2);
vector<Peak> test;
test.push_back(peak1);
test.push_back(peak2);
return test;
}
peak.hpp
#ifndef PEAK_H
#define PEAK_H
class Peak {
public:
float freq;
float mag;
Peak() : freq(), mag() {}
Peak(float f, float m) : freq(f), mag(m) {}
};
#endif
peak_detection_.pyx
# distutils: language = c++
# distutils: sources = peak_detection.cpp
from libcpp.vector cimport vector
from libcpp.map cimport map
from libcpp.string cimport string
cdef extern from "peak.hpp":
cdef cppclass Peak:
Peak()
Peak(Peak &)
float freq, mag
cdef class PyPeak:
cdef Peak *thisptr
def __cinit__(self):
self.thisptr = new Peak()
def __dealloc__(self):
del self.thisptr
cdef copy(self, Peak &other):
del self.thisptr
self.thisptr = new Peak(other)
def __repr__(self):
return "<Peak: freq={0}, mag={1}>".format(self.freq, self.mag)
property freq:
def __get__(self): return self.thisptr.freq
def __set__(self, freq): self.thisptr.freq = freq
property mag:
def __get__(self): return self.thisptr.mag
def __set__(self, mag): self.thisptr.mag = mag
cdef extern from "peak_detection.hpp":
cdef cppclass PeakDetection:
PeakDetection(map[string,string])
vector[Peak] getPeaks(vector[float])
cdef class PyPeakDetection:
cdef PeakDetection *thisptr
def __cinit__(self, map[string,string] config):
self.thisptr = new PeakDetection(config)
def __dealloc__(self):
del self.thisptr
def getPeaks(self, data):
cdef Peak peak
cdef PyPeak new_peak
cdef vector[Peak] peaks = self.thisptr.getPeaks(data)
retval = []
for peak in peaks:
new_peak = PyPeak()
new_peak.copy(peak) # how can I avoid that copy?
retval.append(new_peak)
return retval
有两个项目完成了将 C++ 代码连接到 Python 的过程,它们经受住了时间 Boost.Python and SWIG 的考验。两者都通过向相关 C/C++ 代码添加额外标记并生成动态加载的 python 扩展库(.so 文件)和相关 python 模块来工作。
但是,根据您的用例,可能仍然会有一些额外的标记,看起来像 "copying." 但是,复制不应该那么广泛,并且它们将全部暴露在 C++ 代码中,而不是显式显示在 Cython/Pyrex.
中逐字复制
如果您有一个现代 C++ 编译器并且可以使用右值引用、移动构造函数和 std::move 它非常简单。我认为最简单的方法是为向量创建一个 Cython 包装器,然后使用移动构造函数来获取向量的内容。
显示的所有代码都在 peak_detection_.pyx。
先换行std::move
。为简单起见,我只包装了我们想要的一个案例 (vector<Peak>
),而不是乱用模板。
cdef extern from "<utility>":
vector[Peak]&& move(vector[Peak]&&) # just define for peak rather than anything else
其次,创建矢量包装器class。这定义了像列表一样访问它所必需的 Python 函数。它还定义了一个函数来调用移动赋值运算符
cdef class PyPeakVector:
cdef vector[Peak] vec
cdef move_from(self, vector[Peak]&& move_this):
self.vec = move(move_this)
def __getitem__(self,idx):
return PyPeak2(self,idx)
def __len__(self):
return self.vec.size()
然后定义 class 换行 Peak
。这与您的其他 class 略有不同,因为它不拥有它包装的 Peak
(矢量拥有)。否则,大部分功能保持不变
cdef class PyPeak2:
cdef int idx
cdef PyPeakVector vector # keep this alive, since it owns the peak rather that PyPeak2
def __cinit__(self,PyPeakVector vec,idx):
self.vector = vec
self.idx = idx
cdef Peak* getthisptr(self):
# lookup the pointer each time - it isn't generally safe
# to store pointers incase the vector is resized
return &self.vector.vec[self.idx]
# rest of functions as is
# don't define a destructor since we don't own the Peak
最后,执行getPeaks()
cdef class PyPeakDetection:
# ...
def getPeaks(self, data):
cdef Peak peak
cdef PyPeak new_peak
cdef vector[Peak] peaks = self.thisptr.getPeaks(data)
retval = PyPeakVector()
retval.move_from(move(peaks))
return retval
替代方法:
如果 Peak
很重要,您可以采用一种方法,在构建 PyPeak
时,在 Peak
上调用 move
而不是在向量上调用。对于这里的情况,移动和复制将等效于 `Peak.
如果您不能使用 C++11 功能,则需要稍微更改一下界面。不是让你的 C++ getPeaks
函数 return 一个向量,而是将一个空向量引用(由 PyPeakVector
拥有)作为输入参数并写入它。其余大部分包装保持不变。
在 PyPeakDetection.getPeaks
中由 self.thisptr.getPeaks(data)
编辑的峰值 return(在 peak_detection_.pyx 中定义)?
peak_detection.hpp
#ifndef PEAKDETECTION_H
#define PEAKDETECTION_H
#include <string>
#include <map>
#include <vector>
#include "peak.hpp"
class PeakDetection
{
public:
PeakDetection(std::map<std::string, std::string> config);
std::vector<Peak> getPeaks(std::vector<float> &data);
private:
float _threshold;
};
#endif
peak_detection.cpp
#include <iostream>
#include <string>
#include "peak.hpp"
#include "peak_detection.hpp"
using namespace std;
PeakDetection::PeakDetection(map<string, string> config)
{
_threshold = stof(config["_threshold"]);
}
vector<Peak> PeakDetection::getPeaks(vector<float> &data){
Peak peak1 = Peak(10,1);
Peak peak2 = Peak(20,2);
vector<Peak> test;
test.push_back(peak1);
test.push_back(peak2);
return test;
}
peak.hpp
#ifndef PEAK_H
#define PEAK_H
class Peak {
public:
float freq;
float mag;
Peak() : freq(), mag() {}
Peak(float f, float m) : freq(f), mag(m) {}
};
#endif
peak_detection_.pyx
# distutils: language = c++
# distutils: sources = peak_detection.cpp
from libcpp.vector cimport vector
from libcpp.map cimport map
from libcpp.string cimport string
cdef extern from "peak.hpp":
cdef cppclass Peak:
Peak()
Peak(Peak &)
float freq, mag
cdef class PyPeak:
cdef Peak *thisptr
def __cinit__(self):
self.thisptr = new Peak()
def __dealloc__(self):
del self.thisptr
cdef copy(self, Peak &other):
del self.thisptr
self.thisptr = new Peak(other)
def __repr__(self):
return "<Peak: freq={0}, mag={1}>".format(self.freq, self.mag)
property freq:
def __get__(self): return self.thisptr.freq
def __set__(self, freq): self.thisptr.freq = freq
property mag:
def __get__(self): return self.thisptr.mag
def __set__(self, mag): self.thisptr.mag = mag
cdef extern from "peak_detection.hpp":
cdef cppclass PeakDetection:
PeakDetection(map[string,string])
vector[Peak] getPeaks(vector[float])
cdef class PyPeakDetection:
cdef PeakDetection *thisptr
def __cinit__(self, map[string,string] config):
self.thisptr = new PeakDetection(config)
def __dealloc__(self):
del self.thisptr
def getPeaks(self, data):
cdef Peak peak
cdef PyPeak new_peak
cdef vector[Peak] peaks = self.thisptr.getPeaks(data)
retval = []
for peak in peaks:
new_peak = PyPeak()
new_peak.copy(peak) # how can I avoid that copy?
retval.append(new_peak)
return retval
有两个项目完成了将 C++ 代码连接到 Python 的过程,它们经受住了时间 Boost.Python and SWIG 的考验。两者都通过向相关 C/C++ 代码添加额外标记并生成动态加载的 python 扩展库(.so 文件)和相关 python 模块来工作。
但是,根据您的用例,可能仍然会有一些额外的标记,看起来像 "copying." 但是,复制不应该那么广泛,并且它们将全部暴露在 C++ 代码中,而不是显式显示在 Cython/Pyrex.
中逐字复制如果您有一个现代 C++ 编译器并且可以使用右值引用、移动构造函数和 std::move 它非常简单。我认为最简单的方法是为向量创建一个 Cython 包装器,然后使用移动构造函数来获取向量的内容。
显示的所有代码都在 peak_detection_.pyx。
先换行std::move
。为简单起见,我只包装了我们想要的一个案例 (vector<Peak>
),而不是乱用模板。
cdef extern from "<utility>":
vector[Peak]&& move(vector[Peak]&&) # just define for peak rather than anything else
其次,创建矢量包装器class。这定义了像列表一样访问它所必需的 Python 函数。它还定义了一个函数来调用移动赋值运算符
cdef class PyPeakVector:
cdef vector[Peak] vec
cdef move_from(self, vector[Peak]&& move_this):
self.vec = move(move_this)
def __getitem__(self,idx):
return PyPeak2(self,idx)
def __len__(self):
return self.vec.size()
然后定义 class 换行 Peak
。这与您的其他 class 略有不同,因为它不拥有它包装的 Peak
(矢量拥有)。否则,大部分功能保持不变
cdef class PyPeak2:
cdef int idx
cdef PyPeakVector vector # keep this alive, since it owns the peak rather that PyPeak2
def __cinit__(self,PyPeakVector vec,idx):
self.vector = vec
self.idx = idx
cdef Peak* getthisptr(self):
# lookup the pointer each time - it isn't generally safe
# to store pointers incase the vector is resized
return &self.vector.vec[self.idx]
# rest of functions as is
# don't define a destructor since we don't own the Peak
最后,执行getPeaks()
cdef class PyPeakDetection:
# ...
def getPeaks(self, data):
cdef Peak peak
cdef PyPeak new_peak
cdef vector[Peak] peaks = self.thisptr.getPeaks(data)
retval = PyPeakVector()
retval.move_from(move(peaks))
return retval
替代方法:
如果 Peak
很重要,您可以采用一种方法,在构建 PyPeak
时,在 Peak
上调用 move
而不是在向量上调用。对于这里的情况,移动和复制将等效于 `Peak.
如果您不能使用 C++11 功能,则需要稍微更改一下界面。不是让你的 C++ getPeaks
函数 return 一个向量,而是将一个空向量引用(由 PyPeakVector
拥有)作为输入参数并写入它。其余大部分包装保持不变。