2012-07-12 95 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