【问题标题】:How to handle C++ return type std::vector<int> in Python ctypes?如何在 Python ctypes 中处理 C++ 返回类型 std::vector<int>?
【发布时间】:2013-05-28 22:21:02
【问题描述】:

我找不到 ctypes 将如何弥合std::vector 和 Python 之间的差距;互联网上没有提到的组合。这是不好的做法,是不存在还是我遗漏了什么?

C++ : xxx.cpp

#include <fstream>
#include <string>
using namespace std;
extern "C" std::vector<int> foo(const char* FILE_NAME)
{
    string line;
    std::vector<int> result;
    ifstream myfile(FILE_NAME);
    while (getline(myfile, line)) {
      result.push_back(1);
    }

    return(result);
}

Python: xxx.py

import ctypes
xxx = ctypes.CDLL("./libxxx.so")
xxx.foo.argtypes = ??????
xxx.foo.restype = ??????

【问题讨论】:

  • 我刚从那个页面出来,它的相关性就像一个 Python Numpy 问题与另一个 Python Numpy 问题相关。在这种情况下,提到的线程没有帮助。其他建议?
  • 请注意,它确实提到了 std::vector,但在将其连接到 python 中的列表的意义上。我的理解是,使用 ctypes,Python 将能够使用 ctypes-object?
  • 它与使用 std::vector 的关系不如与使用 C++ 的关系。 ctypes 不以任何有意义的方式支持 C++。最好使用 C 风格的接口创建 C 函数。
  • 你可能会一起破解一些可行的东西,但你最好创建一个 int 数组并返回一个指向它的指针。

标签: c++ python ctypes


【解决方案1】:

无论这种方法实际上是否提供了更快的执行时间,我都会解释一下如何去做。基本上,创建一个指向 C++ vector 的指针,它可以通过 C 函数与 Python 交互。然后,您可以将 C++ 代码包装在 Python 类中,隐藏 ctypes 的实现细节。

我在 Python 类中包含了我认为有用的魔法方法。您可以选择删除它们或添加更多以满足您的需要。不过要保留析构函数很重要。

C++

// vector_python.cpp
#include <vector>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

extern "C" void foo(vector<int>* v, const char* FILE_NAME){
    string line;
    ifstream myfile(FILE_NAME);
    while (getline(myfile, line)) v->push_back(1);
}

extern "C" {
    vector<int>* new_vector(){
        return new vector<int>;
    }
    void delete_vector(vector<int>* v){
        cout << "destructor called in C++ for " << v << endl;
        delete v;
    }
    int vector_size(vector<int>* v){
        return v->size();
    }
    int vector_get(vector<int>* v, int i){
        return v->at(i);
    }
    void vector_push_back(vector<int>* v, int i){
        v->push_back(i);
    }
}

将其编译为共享库。在 Mac OS X 上,这可能看起来像,

g++ -c -fPIC vector_python.cpp -o vector_python.o
g++ -shared -Wl,-install_name,vector_python_lib.so -o vector_python_lib.so vector_python.o

Python

from ctypes import *

class Vector(object):
    lib = cdll.LoadLibrary('vector_python_lib.so') # class level loading lib
    lib.new_vector.restype = c_void_p
    lib.new_vector.argtypes = []
    lib.delete_vector.restype = None
    lib.delete_vector.argtypes = [c_void_p]
    lib.vector_size.restype = c_int
    lib.vector_size.argtypes = [c_void_p]
    lib.vector_get.restype = c_int
    lib.vector_get.argtypes = [c_void_p, c_int]
    lib.vector_push_back.restype = None
    lib.vector_push_back.argtypes = [c_void_p, c_int]
    lib.foo.restype = None
    lib.foo.argtypes = [c_void_p]

    def __init__(self):
        self.vector = Vector.lib.new_vector()  # pointer to new vector

    def __del__(self):  # when reference count hits 0 in Python,
        Vector.lib.delete_vector(self.vector)  # call C++ vector destructor

    def __len__(self):
        return Vector.lib.vector_size(self.vector)

    def __getitem__(self, i):  # access elements in vector at index
        if 0 <= i < len(self):
            return Vector.lib.vector_get(self.vector, c_int(i))
        raise IndexError('Vector index out of range')

    def __repr__(self):
        return '[{}]'.format(', '.join(str(self[i]) for i in range(len(self))))

    def push(self, i):  # push calls vector's push_back
        Vector.lib.vector_push_back(self.vector, c_int(i))

    def foo(self, filename):  # foo in Python calls foo in C++
        Vector.lib.foo(self.vector, c_char_p(filename))

然后您可以在解释器中对其进行测试(file.txt 仅包含三行乱码)。

>>> from vector import Vector
>>> a = Vector()
>>> a.push(22)
>>> a.push(88)
>>> a
[22, 88]
>>> a[1]
88
>>> a[2]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "vector.py", line 30, in __getitem__
    raise IndexError('Vector index out of range')
IndexError: Vector index out of range
>>> a.foo('file.txt')
>>> a
[22, 88, 1, 1, 1]
>>> b = Vector()
>>> ^D
destructor called in C++ for 0x1003884d0
destructor called in C++ for 0x10039df10

【讨论】:

  • 超级棒的答案!
【解决方案2】:

特别的原因是速度很重要。我正在创建一个 应该能够处理大数据的应用程序。在 200,000 行上 必须根据 300 个值(200k x 300 矩阵)计算缺失值。一世 相信,但如果我错了,请纠正我,C++ 将显着 更快。

好吧,如果您从一个大文件中读取,您的进程将主要受 IO 限制,因此 Python 和 C 之间的时间安排可能不会有显着差异。

以下代码...

result = []
for line in open('test.txt'):
    result.append(line.count('NA'))

...运行起来似乎和我可以用 C 一起破解的任何东西一样快,尽管它使用了一些我不太熟悉的 optimized algorithm

处理 200,000 行只需要不到一秒的时间,不过我很想看看你是否可以编写一个更快的 C 函数。


更新

如果您想在 C 中执行此操作,并最终得到一个 Python 列表,那么使用 Python/C API 自己构建列表可能更有效,而不是构建一个 std::vector 然后稍后转换为 Python 列表开。

一个只返回从 0 到 99 的整数列表的示例...

// hack.c

#include <python2.7/Python.h>

PyObject* foo(const char* filename)
{
    PyObject* result = PyList_New(0);
    int i;

    for (i = 0; i < 100; ++i)
    {
        PyList_Append(result, PyInt_FromLong(i));
    }

    return result;
}

编译...

$ gcc -c hack.c -fPIC
$ ld -o hack.so -shared hack.o -lpython2.7

使用示例...

>>> from ctypes import *
>>> dll = CDLL('./hack.so')
>>> dll.foo.restype = py_object
>>> dll.foo('foo')
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, ...]

【讨论】:

  • 我也注意到了这一点,刚才运行类似的代码,Python 花了大约 8 秒来完成它。但是,我仍然对正确的 C++ 方法感到好奇。
  • @Dualinity 你希望 Python 中的结果如何?作为 Python 列表?
  • 1 秒是惊人的。我不在乎它是否对其他寻找 C++ 答案的人没有太大帮助,这令人印象深刻。让我想知道什么时候我真的需要 C++。
  • @Dualinity 查看基于 C 的示例的更新答案。在 C++ 中使用类似的代码应该很简单。
【解决方案3】:

基本上,从动态加载的库中返回 C++ 对象并不是一个好主意。要在 Python 代码中使用 C++ vector,您必须教 Python 处理 C++ 对象(这包括对象的二进制表示,可能会随着新版本的 C++ 编译器或 STL 而改变)。

ctypes 允许您与使用 C 类型的库进行交互。不是 C++。

也许问题可以通过boost::python 解决,但使用纯 C 进行交互看起来更可靠。

【讨论】:

    猜你喜欢
    • 2018-06-28
    • 2022-10-25
    • 1970-01-01
    • 2022-01-03
    • 1970-01-01
    • 2020-03-06
    • 2016-11-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多