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