有可能與__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