如何公开一个函数返回一个C ++对象到Python,而不复制对象? [英] How to expose a function returning a C++ object to Python without copying the object?

查看:235
本文介绍了如何公开一个函数返回一个C ++对象到Python,而不复制对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

另一个问题中,我学习了如何通过复制对象来公开将C ++对象返回到Python的函数。不必执行副本似乎不是最佳的。如何在不复制对象的情况下返回对象?即,如何直接访问中的 self.thisptr.getPeaks(data)返回的峰 c> PyPeakDetection.getPeaks peak_detection_.pyx )?

In another question I learnt how to expose a function returning a C++ object to Python by copying the object. Having to perform a copy does not seem optimal. How can I return the object without copying it? i.e. how can I directly access the peaks returned by self.thisptr.getPeaks(data) in PyPeakDetection.getPeaks (defined in 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 / p>

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
$ b

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 / p>

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 ++编译器,并可以使用右值引用,移动构造函数和std :: move它很简单。我认为最简单的方法是为向量创建一个Cython包装器,然后使用move构造器来保存向量的内容。

If you have a modern C++ compiler and can use rvalue references, move constructors and std::move it's pretty straight-forward. I think the easiest way is to create a Cython wrapper for the vector, and then use a move constructor to take hold of the contents of the vector.

显示的所有代码in peak_detection_.pyx。

All code shown goes in peak_detection_.pyx.

第一次包装 std :: move 。为了简单起见,我只是包装了我们想要的一种情况( vector< Peak> ),而不是用模板打乱。

First wrap std::move. For simplicity I've just wrapped the one case we want (vector<Peak>) rather than messing about with templates.

cdef extern from "<utility>":
    vector[Peak]&& move(vector[Peak]&&) # just define for peak rather than anything else

,创建一个向量包装类。它定义了像列表一样访问它所需的Python函数。它还定义了一个函数来调用移动赋值运算符

Second, create a vector wrapper class. This defines the Python functions necessary to access it like a list. It also defines a function to call the move assignment operator

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()

然后定义包装 Peak 的类。这与你的其他类略有不同,因为它不拥有 Peak 它包装(向量是)。否则,大多数函数保持不变

Then define the class the wraps the Peak. This is slightly different to your other class in that it doesn't own the Peak it wraps (the vector does). Otherwise, most of the functions remain the same

cdef class PyPeak2:
    cdef Peak* thisptr
    cdef PyPeakVector vector # keep this alive, since it owns the peak rather that PyPeak2

    def __cinit__(self,PyPeakVector vec,idx):
        self.vector = vec
        self.thisptr = &vec.vec[idx]

    # rest of functions as is

    # don't define a destructor since we don't own the Peak

def 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()



最后,实现 getPeaks code>

Finally, implement 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 上创建 code> s。对于这里的移动和复制将等同于'峰值。

If Peak was nontrivial you could go for an approach where you call move on Peak rather that on the vector, as you construct your PyPeaks. For the case you have here move and copy will be equivalent for `Peak.

如果你不能使用C ++ 11的功能,你需要改变接口一点。不是让你的C ++ getPeaks 函数返回一个向量,它需要一个空的向量引用(由 PyPeakVector 拥有)输入参数并写入。包装的其余部分大部分保持不变。

If you can't use C++11 features you'll need to change the interface a little. Instead of having your C++ getPeaks function return a vector have it take an empty vector reference (owned by PyPeakVector) as an input argument and write into it. Much of the rest of the wrapping remains the same.

这篇关于如何公开一个函数返回一个C ++对象到Python,而不复制对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆