我有一个类似于以下的类:
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做绑定。
谢谢。
使用__getattr__
和自定义%MethodCode
是可能的;然而,有几点需要考虑:
- 需要创建一个中间类型/对象,因为
a.x
将返回一个提供__getitem__
和__setitem__
的对象。当出现越界时,这两种方法都应该引发一个IndexError
,因为这是用于通过__getitem__
迭代的旧协议的一部分;如果没有它,在a.x
上迭代时将发生崩溃 -
为了保证向量的生存期,
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中设计类,以提供使用绑定的python接口
虽然这种方法有一些缺点,比如代码被分离成更多的文件,这些文件可能需要与库一起分发,但它确实提供了一些主要的好处:
- 在python中实现python接口比在C或互操作性库的接口中实现要容易得多
- 对切片、迭代器等的支持可以在python中更自然地实现,而不必通过C API进行管理
- 可以利用python的垃圾收集器来管理底层内存的生存期
- python接口与用于提供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中却有一些困难:
- Python不支持重载方法,当参数类型/计数相同时,支持重载的习惯用法将失败
- 引用double(Python中的float)的概念在这两种语言之间是不同的。在Python中,float是不可变的类型,因此其值不能更改。例如,在Python中,语句
n = a.x[0]
绑定n
以引用从a.x[0]
返回的float
对象。指派CCD_ 22重新绑定CCD_ 23以引用CCD_;则不将CCD_ 25设置为CCD_ __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创建了我们想要的interface。可以让绑定提供接口;然而,这可能需要对两种语言之间的差异和绑定实现有充分的了解。
>>>来自示例导入A>>>a=a()>>>对于a.x中的x:…打印x。。。024>>>a.x[0]=4>>>对于a.x中的x:…打印x。。。424>>>x=a.x>>>a=无>>>打印x[0]4.0