6

次のようなクラスがあります。

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を使用しています。

ありがとう。

4

1 に答える 1

8

__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 バインディングを設計します。
  • バインディングを使用する pythonic インターフェイスを提供するために、python でクラスを設計します。

このアプローチにはいくつかの欠点があります。たとえば、コードが複数のファイルに分割され、ライブラリと一緒に配布する必要がある場合がありますが、いくつかの大きな利点があります。

  • C や相互運用性ライブラリのインターフェイスよりも、Python で Pythonic インターフェイスを実装する方がはるかに簡単です。
  • スライシング、イテレータなどのサポートは、C API で管理するのではなく、Python でより自然に実装できます。
  • 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) への参照の概念は、2 つの言語間で異なります。Python では、float は不変型であるため、値を変更することはできません。たとえば、Python では、ステートメントは から返されたオブジェクトを参照 するようにn = a.x[0]バインドされます。代入は、オブジェクトを参照するために再バインドします。に設定されていません。nfloata.x[0]n = 4nint(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。これは最高のインターフェースではありません。バインディングは、目的のインターフェースではなく、必要な機能を提供するように設計されています。

次に、モジュールA内にクラスを作成する簡単なバインディングをいくつか書きましょう。cexample

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中間クラスのため、どちらのバインディングもかなり単純です。%MethodCodeさらに、このアプローチでは、ブロック内のコードが少なくて済むため、SIP および Python C API の知識が少なくて済みます。

最後に、example.py目的の pythonic インターフェイスを提供するものを作成します。

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 は必要なインターフェイスを作成します。バインディングにインターフェースを提供させることは可能です。ただし、これには、2 つの言語とバインディングの実装の違いを十分に理解している必要があります。

>>>インポート例Aより
>>> a = A()
>>> 斧の x の場合:
... x を印刷
...
0.0
2.0
4.0
>>> ax[0] = 4
>>> 斧の x の場合:
... x を印刷
...
4.0
2.0
4.0
>>> x = 斧
>>> a = なし
>>> x[0] を出力
4.0
于 2012-07-13T14:07:33.447 に答える