¿Cómo manejar el tipo de retorno de C++ std::vector<int> en ctypes de Python?
No puedo encontrar cómo ctypes cerrará la brecha entre std::vector
Python y Python; En ningún lugar de Internet se menciona la combinación. ¿Es esta una mala práctica, no existe o me falta algo?
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 = ??????
Ya sea que este enfoque proporcione o no un tiempo de ejecución más rápido, explicaré un poco cómo podría hacerlo. Básicamente, cree un puntero a C++ vector
que pueda interactuar con Python a través de funciones de C. Luego puede empaquetar el código C++ en una clase de Python, ocultando los detalles de implementación de ctypes
.
Incluí lo que pensé que serían métodos mágicos útiles para incluir en la clase de Python. Puede optar por eliminarlos o agregar más según sus necesidades. Sin embargo, es importante conservar el destructor.
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);
}
}
Compílelo como una biblioteca compartida. En Mac OS X esto podría verse así:
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
Pitón
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))
Luego puedes probarlo en el intérprete ( file.txt
solo consta de tres líneas de galimatías).
>>> 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
La razón particular es que la velocidad es importante. Estoy creando una aplicación que debería poder manejar big data. En 200.000 filas, los faltantes deben contarse en 300 valores (matriz de 200k por 300). Creo, pero corríjanme si me equivoco, que C++ será significativamente más rápido.
Bueno, si estás leyendo un archivo grande, tu proceso estará principalmente vinculado a IO, por lo que los tiempos entre Python y C probablemente no serán significativamente diferentes.
El siguiente código...
result = []
for line in open('test.txt'):
result.append(line.count('NA'))
...parece ejecutarse tan rápido como cualquier cosa que pueda hackear en C, aunque utiliza algún algoritmo optimizado con el que no estoy muy familiarizado.
Se necesitan menos de un segundo para procesar 200.000 líneas, aunque me interesaría ver si se puede escribir una función C que sea significativamente más rápida.
Actualizar
Si desea hacerlo en C y terminar con una lista de Python, probablemente sea más eficiente usar la API de Python/C para crear la lista usted mismo, en lugar de crear una lista de Python y std::vector
luego convertirla a una lista de Python.
Un ejemplo que simplemente devuelve una lista de números enteros del 0 al 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;
}
Compilado con...
$ gcc -c hack.c -fPIC
$ ld -o hack.so -shared hack.o -lpython2.7
Ejemplo de uso...
>>> 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, ...]
Básicamente, devolver un objeto C++ desde una biblioteca cargada dinámicamente no es una buena idea. Para usar C++ vector
en código Python, debe enseñarle a Python a manejar objetos C++ (y esto incluye la representación binaria de los objetos que pueden cambiar con la nueva versión de un compilador de C++ o STL).
ctypes
le permite interactuar con una biblioteca usando tipos C. No C++.
Tal vez el problema se pueda resolver mediante boost::python
, pero parece más confiable usar C simple para la interacción.