E 'possibile con __getattr__
e personalizzato %MethodCode
; Tuttavia, ci sono alcuni punti da prendere in considerazione:
- Un tipo/oggetto intermedio deve essere creato, come
a.x
restituirà un oggetto che fornisce __getitem__
e __setitem__
. Entrambi i metodi dovrebbero generare un IndexError
quando si verifica un fuori limite, poiché questo fa parte del vecchio protocollo utilizzato per iterare tramite __getitem__
; senza di esso, si verificherebbe un arresto anomalo quando si itera su a.x
.
Per garantire la durata del vettore, l'oggetto a.x
deve mantenere un riferimento all'oggetto che possiede il vettore (a
). Si consideri il seguente codice:
a = A()
x = a.x
a = None # If 'x' has a reference to 'a.v' and not 'a', then it may have a
# dangling reference, as 'a' is refcounted by python, and 'a.v' is
# not refcounted.
scrittura %MethodCode
può essere difficile, specialmente quando a dover gestire il conteggio di riferimento durante i casi di errore. Richiede una comprensione dell'API Python e del SIP.
Per una soluzione alternativa, considerare:
- design binding Python per fornire funzionalità.
- Classe di progettazione (i) in python per fornire l'interfaccia pythonic che utilizza i binding.
Mentre l'approccio ha alcuni inconvenienti, come ad esempio il codice è suddiviso in più file che possono avere bisogno di essere distribuito con la biblioteca, fornisce alcuni importanti vantaggi:
- è molto più facile implementare un'interfaccia pythonic in python piuttosto che in C o nell'interfaccia della libreria di interoperabilità.
- Help affettatura, iteratori, ecc per può essere più naturalmente realizzato in pitone, invece di dover gestire tramite l'API C.
- Può sfruttare il garbage collector di python per gestire la durata della memoria sottostante.
- L'interfaccia pythonic è disaccoppiata da qualsiasi implementazione viene utilizzata per fornire l'interoperabilità tra python e C++.Con un'interfaccia vincolante più semplice e più semplice, cambiare tra le implementazioni, come Boost.Python e SIP, è molto più semplice.
Ecco un walk-through dimostra questo approccio. Innanzitutto, iniziamo con la classe base A
. In questo esempio, ho fornito un costruttore che imposterà alcuni dati iniziali.
a.hpp
:
#ifndef A_HPP
#define A_HPP
#include <vector>
class A
{
std::vector<double> v;
public:
A() { for (int i = 0; i < 6; ++i) v.push_back(i); }
double& x(int i) { return v[2*i]; }
double x(int i) const { return v[2*i]; }
double& y(int i) { return v[2*i+1]; }
double y(int i) const { return v[2*i+1]; }
std::size_t size() const { return v.size()/2; }
};
#endif // A_HPP
Prima di fare le associazioni, consente di esaminare l'interfaccia A
. Mentre è un'interfaccia facile da usare in C++, ha qualche difficoltà in pitone:
- Python non supporta i metodi di overload, e modi di dire per sostenere sovraccarico avrà esito negativo quando il tipo di argomento/conta sono gli stessi.
- Il concetto di un riferimento a un doppio (float in Python) è diverso tra le due lingue. In Python, il float è un tipo immutabile, quindi il suo valore non può essere modificato. Ad esempio, in Python l'istruzione
n = a.x[0]
associa n
per fare riferimento all'oggetto float
restituito da a.x[0]
. L'assegnazione n = 4
rebinds n
per fare riferimento all'oggetto int(4)
; non imposta a.x[0]
a 4
.
__len__
si aspetta int
, non std::size_t
.
Consente di creare una classe intermedia di base che semplificherà le associazioni.
pya.hpp
:
#ifndef PYA_HPP
#define PYA_HPP
#include "a.hpp"
struct PyA: A
{
double get_x(int i) { return x(i); }
void set_x(int i, double v) { x(i) = v; }
double get_y(int i) { return y(i); }
void set_y(int i, double v) { y(i) = v; }
int length() { return size(); }
};
#endif // PYA_HPP
Grande! PyA
ora fornisce funzioni membro che non restituiscono riferimenti e la lunghezza viene restituita come int
. Non è la migliore delle interfacce, i binding sono progettati per fornire la funzionalità necessaria, anziché l'interfaccia desiderata.
Ora, scriviamo alcuni semplici collegamenti che creeranno la classe A
nel modulo cexample
.
Ecco le associazioni in SIP:
%Module cexample
class PyA /PyName=A/
{
%TypeHeaderCode
#include "pya.hpp"
%End
public:
double get_x(int);
void set_x(int, double);
double get_y(int);
void set_y(int, double);
int __len__();
%MethodCode
sipRes = sipCpp->length();
%End
};
O se preferite Boost.Python:
#include "pya.hpp"
#include <boost/python.hpp>
BOOST_PYTHON_MODULE(cexample)
{
using namespace boost::python;
class_<PyA>("A")
.def("get_x", &PyA::get_x )
.def("set_x", &PyA::set_x )
.def("get_y", &PyA::get_y )
.def("set_y", &PyA::set_y )
.def("__len__", &PyA::length)
;
}
A causa della classe intermedia PyA
, entrambi gli attacchi sono abbastanza semplici. Inoltre, questo approccio richiede meno conoscenze API SIP e Python C poiché richiede meno codice all'interno dei blocchi %MethodCode
.
Infine, creare example.py
che fornirà l'interfaccia divinatorio desiderata:
class A:
class __Helper:
def __init__(self, data, getter, setter):
self.__data = data
self.__getter = getter
self.__setter = setter
def __getitem__(self, index):
if len(self) <= index:
raise IndexError("index out of range")
return self.__getter(index)
def __setitem__(self, index, value):
if len(self) <= index:
raise IndexError("index out of range")
self.__setter(index, value)
def __len__(self):
return len(self.__data)
def __init__(self):
import cexample
a = cexample.A()
self.x = A.__Helper(a, a.get_x, a.set_x)
self.y = A.__Helper(a, a.get_y, a.set_y)
Alla fine, le associazioni forniscono la funzionalità abbiamo bisogno, e pitone crea l'interfaccia che vogliamo. È possibile che i binding forniscano l'interfaccia; tuttavia, ciò può richiedere una comprensione approfondita delle differenze tra le due lingue e l'implementazione del bind.
>>> from example import A
>>> a = A()
>>> for x in a.x:
... print x
...
0.0
2.0
4.0
>>> a.x[0] = 4
>>> for x in a.x:
... print x
...
4.0
2.0
4.0
>>> x = a.x
>>> a = None
>>> print x[0]
4.0