2012-07-12 58 views
5

我有類似下面這樣的類綁定:的Python C++運算符重載

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]; } 
} 

我想有以下Python代碼的工作:

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

我在想__setattr____getattr__,但不知道它是否有效。另一種方法是執行下面的Python:

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

並不像以前一樣好,但可能會更容易實現(與__slice__?)。

PS。我正在使用SIP進行綁定。

謝謝。

回答

6

有可能與__getattr__和自定義%MethodCode;但是,也有幾點需要考慮:

  • 中間類型/對象需要被創建,如a.x將返回一個對象,它提供__getitem____setitem__。當出現越界時,兩種方法應該引發IndexError,因爲這是用於通過__getitem__迭代的舊協議的一部分;沒有它,當迭代a.x時會發生崩潰。
  • 爲了保證向量的生命週期,對象需要維護對擁有向量的對象(a)的引用。請看下面的代碼:

    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. 
    
  • %MethodCode是很困難的,其在錯誤的情況下管理引用計數時尤其如此。它需要了解Python C API和SIP。

對於一個替代解決方案,可以考慮:

  • 設計Python綁定來提供功能。
  • python中的設計類提供使用綁定的pythonic接口。

雖然這種方法有一些缺點,如代碼分爲可能需要與庫分佈多個文件,但它確實提供了一些主要好處:

  • 這是很容易在python中實現一個pythonic接口,而不是在C或互操作性庫的接口中實現。
  • 支持切片,迭代器等可以更自然地在python中實現,而不必通過C API進行管理。
  • 可以利用python的垃圾回收器來管理底層內存的生命週期。
  • Pythonic接口與用於提供python和C++之間的互操作性的任何實現都是分離的。通過更扁平和更簡單的綁定接口,在諸如Boost.Python和SIP之類的實現之間進行切換要容易得多。

下面是一個步行通過展示這種方法。首先,我們從基本的A課程開始。在這個例子中,我提供了一個構造函數來設置一些初始數據。

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 

做綁定之前,讓我們檢查A接口。雖然這是一個簡單的界面在C++中使用,它在蟒蛇一些困難:

  • Python不支持重載方法,成語支持重載時的參數類型/數是相同的將失敗。
  • 對這兩種語言的double(float in Python)引用的概念是不同的。在Python中,float是一個不可變類型,所以它的值不能被改變。例如,在Python中,語句n = a.x[0]綁定n以引用從a.x[0]返回的float對象。賦值n = 4重新綁定n以引用int(4)對象;它不會將a.x[0]設置爲4
  • __len__預計int,不std::size_t

讓我們創建一個基本的中級班,這將有助於簡化綁定。

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 

太好了! PyA現在提供了不返回引用的成員函數,並且返回長度爲int。這不是最好的接口,所述綁定被設計成提供所需的功能,而不是期望的接口

現在,讓我們寫一些簡單的綁定將在cexample模塊中創建A類。

這裏是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 
}; 

或者如果你喜歡的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) 
    ; 
} 

由於PyA的中間階層,無論綁定的是相當簡單的。此外,這種方法需要較少的SIP和Python C API知識,因爲它需要%MethodCode塊內的代碼較少。

最後,創建example.py將提供所需的Python的接口:

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) 

最後,綁定提供功能我們需要和Python創建接口我們想要的。有可能讓綁定提供接口;然而,這可能需要對兩種語言之間的區別和約束實施有深入的瞭解。

>>> 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