我有一个类似于以下的类:
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__
?)。附注。我正在使用 sip 进行绑定(bind)。
谢谢。
最佳答案
可以使用 __getattr__
和定制 %MethodCode
;但是,有几点需要考虑:
a.x
将返回一个提供 __getitem__
的对象和 __setitem__
.这两种方法都应该引发 IndexError
当越界发生时,因为这是用于通过 __getitem__
进行迭代的旧协议(protocol)的一部分;没有它,在迭代 a.x
时会发生崩溃. a.x
对象需要维护对拥有 vector 的对象的引用( 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。 对于替代解决方案,请考虑:
虽然这种方法有一些缺点,例如代码被分成更多的文件,这些文件可能需要与库一起分发,但它确实提供了一些主要的好处:
这是演示此方法的演练。首先,我们从基本的
A
开始类(class)。在这个例子中,我提供了一个构造函数来设置一些初始数据。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
在进行绑定(bind)之前,让我们检查
A
界面。虽然它在 C++ 中是一个易于使用的接口(interface),但它在 python 中存在一些困难:n = a.x[0]
绑定(bind) n
引用 float
从 a.x[0]
返回的对象.任务 n = 4
重新绑定(bind) n
引用 int(4)
目的;它没有设置 a.x[0]
至 4
. __len__
预计 int
,不是 std::size_t
. 让我们创建一个基本的中间类来帮助简化绑定(bind)。
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
.它不是最好的接口(interface),绑定(bind)旨在提供所需的功能,而不是所需的接口(interface)。现在,让我们编写一些简单的绑定(bind)来创建类
A
在 cexample
模块。这是 SIP 中的绑定(bind):
%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
中间类,两个绑定(bind)都相当简单。此外,这种方法需要更少的 SIP 和 Python C API 知识,因为它需要更少的代码 %MethodCode
块。最后,创建
example.py
这将提供所需的pythonic接口(interface):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 )
最后,绑定(bind)提供了我们需要的功能,python 创建了我们想要的接口(interface)。可以让绑定(bind)提供接口(interface);但是,这可能需要对两种语言和绑定(bind)实现之间的差异有深入的了解。
>>> 从示例导入 A
>>> a = A()
>>> 对于 a.x 中的 x:
... 打印 x
...
0.0
2.0
4.0
>>> a.x[0] = 4
>>> 对于 a.x 中的 x:
... 打印 x
...
4.0
2.0
4.0
>>> x = a.x
>>> a = 无
>>> 打印 x[0]
4.0
关于c++ - 用于 C++ 运算符重载的 Python 绑定(bind),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11455578/