__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]
バインドされます。代入は、オブジェクトを参照するために再バインドします。に設定されていません。n
float
a.x[0]
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
。これは最高のインターフェースではありません。バインディングは、目的のインターフェースではなく、必要な機能を提供するように設計されています。
次に、モジュール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