c++ - 用于 C++ 运算符重载的 Python 绑定(bind)

标签 c++ python reference python-sip

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

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 时会发生崩溃.
  • 为了保证vector的生命周期,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。

  • 对于替代解决方案,请考虑:
  • 设计 python 绑定(bind)以提供功能。
  • 在 python 中设计类以提供使用绑定(bind)的 pythonic 接口(interface)。

  • 虽然这种方法有一些缺点,例如代码被分成更多的文件,这些文件可能需要与库一起分发,但它确实提供了一些主要的好处:
  • 在 python 中实现 pythonic 接口(interface)比在 C 或互操作性库的接口(interface)中容易得多。
  • 对切​​片、迭代器等的支持可以在 python 中更自然地实现,而不必通过 C API 来管理它。
  • 可以利用python的垃圾收集器来管理底层内存的生命周期。
  • pythonic 接口(interface)与用于提供 python 和 C++ 之间的互操作性的任何实现分离。使用更扁平和更简单的绑定(bind)接口(interface),在 Boost.Python 和 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 中存在一些困难:
  • Python 不支持重载方法,当参数类型/计数相同时,支持重载的习惯用法将失败。
  • 两种语言之间引用 double (Python 中的浮点数)的概念是不同的。在 Python 中,浮点数是不可变类型,因此它的值不能改变。例如,在 Python 中,语句 n = a.x[0]绑定(bind) n引用 floata.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)来创建类 Acexample模块。

    这是 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/

    相关文章:

    java - 复制迭代器

    c++ - 从 QML 模态对话框中检索值的最佳方法

    c++ - 线程安全计数器c++11

    c++ - 使用 std::accumulate,得到 "too many arguments"错误

    Python 网络脚本不能在 apache 上运行

    python - 如何导出 Sublime 构建工具的路径?

    c++ - 在 C++ 中使用带有 getter 的引用时(接口(interface)/类)

    c++ - 我如何在 VS2010 中创建 COM(C++)服务器?

    python - 一些非常简单的语句,但为什么它会产生 UnboundLocalError

    java - 我的讲师对 Liskov 替换原则的定义不正确,还是我误解了?