12

std::vectorctypes がと 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);
}

パイソン: xxx.py

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

3 に答える 3

24

このアプローチが実際に実行時間を短縮するかどうかにかかわらず、それを行う方法について少し説明します。vector基本的に、 C 関数を介して Python とやり取りできるC++ へのポインターを作成します。その後、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

パイソン

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.txt3 行のジベリッシュで構成されています)。

>>> 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
于 2013-06-02T21:23:10.230 に答える
7

特に理由は、スピードが重要だからです。ビッグデータを処理できるアプリケーションを作成しています。200,000 行では、300 個の値 (200k × 300 マトリックス) で欠落をカウントする必要があります。私は信じていますが、間違っていたら訂正してください。C++ は大幅に高速になるでしょう。

大規模なファイルから読み取る場合、プロセスはほとんど IO バウンドになるため、Python と C の間のタイミングはおそらくそれほど変わらないでしょう。

次のコード...

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

...私がよく知らない最適化されたアルゴリズムを使用していますが、Cで一緒にハックできるものと同じくらい速く実行されるようです。

200,000 行を処理するのに 1 秒もかかりませんが、大幅に高速な C 関数を作成できるかどうかを知りたいです。


アップデート

C でそれを行い、最終的に Python リストを作成する場合は、Python リストを作成して後で Python リストに変換するよりも、 Python/C APIを使用して自分でリストを作成する方がおそらく効率的です。std::vector

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, ...]
于 2013-06-02T20:29:27.760 に答える
3

Basically, returning a C++ object from a dynamically loaded library is not a good idea. To use the C++ vector in Python code, you must teach Python to deal with C++ objects (and this includes binary representation of the objects which can change with new version of a C++ compiler or STL).

ctypes allows you to interact with a library using C types. Not C++.

Maybe the problem is solvable via boost::python, but it looks more reliable to use plain C for the interaction.

于 2013-06-02T17:36:43.307 に答える