2012-07-12 21 views
5

Ho una classe simile al seguente:Python vincolante per C operatore ++ overloading

class A { 
    vector<double> v; 
    double& x(int i) { return v[2*i]; } 
    double& y(int i) { return v[2*i+1]; } 
    double x(int i) const { return v[2*i]; } 
    double y(int i) const { return v[2*i+1]; } 
} 

voglio avere il seguente codice Python lavoro:

a = A() 
a.x[0] = 4 
print a.x[0] 

Stavo pensando di __setattr__ e __getattr__, ma non sono sicuro che funzioni. Un'alternativa è quella di implementare il seguente Python:

a = A() 
a['x', 0] = 4 
print a['x', 0] 

non buono come quello precedente, ma potrebbe essere più facile da implementare (con __slice__?).

PS. Sto usando sorso per fare il binding.

Grazie.

risposta

6

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