繁体   English   中英

用于C ++运算符重载的Python绑定

[英]Python binding for C++ operator overloading

我有一个类似于以下的类:

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__和custom %MethodCode ; 但是,有几点需要考虑:

  • 需要创建一个中间类型/对象,因为ax将返回一个提供__getitem____setitem__的对象。 当超出范围时,两种方法都应该引发IndexError ,因为这是用于通过__getitem__迭代的旧协议的一部分; 没有它,迭代ax时会发生崩溃。
  • 为了保证向量的生命周期, ax对象需要维护对拥有向量( a )的对象的引用。 请考虑以下代码:

     a = A() x = ax a = None # If 'x' has a reference to 'av' and not 'a', then it may have a # dangling reference, as 'a' is refcounted by python, and 'av' 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中有一些困难:

  • Python不支持重载方法,当参数类型/计数相同时,支持重载的惯用语将失败。
  • 引用double(Python中的float)的概念在两种语言之间是不同的。 在Python中,float是一个不可变类型,因此无法更改其值。 例如,在Python中,语句n = ax[0]绑定n以引用从ax[0]返回的float对象。 赋值n = 4重新绑定n以引用int(4)对象; 它没有将ax[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现在提供不返回引用的成员函数,并且length作为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块中需要的代码较少。

最后,创建将提供所需pythonic接口的example.py

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM