2013-06-08 16 views
5

Ho un file di intestazione come:Esporre un vettore come memoryview utilizzando SWIG

#include <vector> 

inline std::vector<uint8_t>& vec() { 
    static std::vector<uint8_t> v { 'a', 'b', 'c', 'd' }; 
    return v; 
} 

inline const std::vector<uint8_t>& cvec() { 
    return vec(); 
} 

posso wrap it in SWIG using std_vector.i and pyabc.i ma che è abbastanza inefficiente (c'è un salto tra C++ e il codice Python per ogni accesso) e dato che questi sono letteralmente solo un mucchio di byte che dovrei essere in grado di avvolgerli con Python's memoryview interface.

Come posso esporre il mio std::vector<uint8_t> come Python memoryview?

risposta

7

L'esposizione come memoryview richiede innanzitutto la creazione di Py_buffer. In Python 3.3+ c'è una comoda funzione di aiuto, PyMemoryView_FromMemory che fa molto del lavoro per noi. Nelle versioni precedenti se abbiamo bisogno di fare qualche passo in più, quindi il nostro typemap base fuori assomiglia:

%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& { 
    Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf); 
    const bool ro = info<$1_type>::is_readonly(); 
    if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) { 
    // error, handle 
    } 
    $result = PyMemoryView_FromBuffer(buf); 
} 

Qui stiamo fondamentalmente l'allocazione della memoria per il Py_buffer. Questo contiene solo i dettagli del buffer internamente per Python. La memoria che assegniamo sarà di proprietà dell'oggetto memoryview una volta creata. Purtroppo poiché verrà rilasciato con una chiamata allo free(), dobbiamo allocarlo con malloc(), anche se è codice C++.

Oltre Py_buffer e opzionale Py_ObjectPyBuffer_FillInfo guadagnato un void* (buffer stesso), la dimensione del buffer, un booleano che indica se è scrivibile e una bandiera. In questo caso il nostro flag indica semplicemente che abbiamo fornito memoria contigua in stile C per il buffer.

Per decidere se è in sola lettura o no, abbiamo utilizzato la variabile integrata $n_type di SWIG e un helper (che potrebbe essere un tratto di tipo C++ 11 se lo volessimo).

Per completare la nostra interfaccia SWIG dobbiamo prevedere che aiutante e includere il file di intestazione, in modo che il tutto diventa:

%module test 

%{ 
#include "test.hh" 

namespace { 
    template <typename T> 
    struct info { 
    static bool is_readonly() { 
     return false; 
    } 
    }; 

    template <typename T> 
    struct info<const T&> { 
    static bool is_readonly() { 
     return true; 
    } 
    }; 
} 
%} 

%typemap(out) std::vector<uint8_t>&, const std::vector<uint8_t>& { 
    Py_buffer *buf=(Py_buffer*)malloc(sizeof *buf); 
    const bool ro = info<$1_type>::is_readonly(); 
    if (PyBuffer_FillInfo(buf, NULL, &((*$1)[0]), (*$1).size(), ro, PyBUF_ND)) { 
    // error, handle 
    } 
    $result = PyMemoryView_FromBuffer(buf); 
} 

%include "test.hh" 

Possiamo quindi verificare con:

import test 

print test.vec() 
print len(test.vec()) 
print test.vec()[0] 
print test.vec().readonly 
test.vec()[0]='z' 
print test.vec()[0] 

print "This should fail:" 
test.cvec()[0] = 0 

che ha funzionato come previsto, testato usando Python 2.7.

Rispetto al semplice wrapping tramite std_vector.i questo approccio presenta alcuni inconvenienti. Il più grande è che non possiamo ridimensionare il vettore, o convertirlo in un vettore in seguito banalmente. Potremmo aggirare questo problema, almeno in parte creando un proxy SWIG per il vettore come normale e utilizzando il secondo parametro di PyBuffer_FillInfo per memorizzarlo internamente. (Questo sarebbe anche necessario se dovessimo gestire la proprietà del vettore per esempio).

Problemi correlati