つまり、B
のラッパーを次のように定義します。
class_<B>( "B", init< A >() )
それ以外の
class_<B>( "B", init< object >() )
Boost.Python(少なくとも1.50)でクラスのラッパーを定義すると、class_
テンプレートは変換関数と構築関数を生成します。これにより、のラッパーA
に変換して構築することができます。A
これらのPyObject
変換には厳密な型チェックがあり、Pythonでは次のことが当てはまる必要がありますisinstance( obj, A )
。
カスタムコンバーターは、以下をサポートするためによく使用されます。
- 既存のPythonタイプとの間の自動変換。たとえば、
std::pair< long, long >
との間で変換しPyTupleObject
ます。
- ダックタイピング。たとえば、互換性のあるインターフェイスを提供する限り、から派生していない
B
クラスを受け入れることができます。D
A
D
B
のインスタンスからの構築A
A
とB
は既存のPython型ではなく、ダックタイピングも必要ないため、カスタムコンバーターは必要ありません。のB
インスタンスを取得するにはA
、を指定するのと同じくらい簡単にすることができinit
ますA
。
これはとの簡略化された例です。ここで、はA
からB
構築B
できますA
。
class A
{
public:
A( long n ) : n_( n ) {};
long n() { return n_; }
private:
long n_;
};
class B
{
public:
B( A a ) : a_( a ) {};
long doSomething() { return a_.n() * 2; }
private:
A a_;
};
そして、ラッパーは次のように定義されます。
using namespace boost::python;
BOOST_PYTHON_MODULE(example)
{
class_< A >( "A", init< long >() )
;
class_<B>( "B", init< A >() )
.def( "doSomething", &B::doSomething )
;
}
B
のラッパーは、A
を介してオブジェクトから構築されることを明示的に示しますinit< A >()
。また、関数A
のラッパーが定義されていないため、のインターフェイスはPythonオブジェクトに完全には公開されていませんA::n()
。
>>> from example import A, B
>>> a = A( 1 )
>>> b = B( a )
>>> b.doSomething()
2
これは、から派生したタイプでも機能しますA
。例えば:
>>> from example import A, B
>>> class C( A ):
... def __init__( self, n ):
... A.__init__( self, n )
...
>>> c = C( 2 )
>>> b = B( c )
>>> b.doSomething()
4
ただし、ダックタイピングは有効になっていません。
>>> from example import A, B
>>> class E: pass
...
>>> e = E()
>>> b = B( e )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
B.__init__(B, instance)
did not match C++ signature:
__init__(_object*, A)
B
に変換可能なオブジェクトから構築しますA
。
B
互換性のあるインターフェイスを提供するオブジェクトから構築できる場合をサポートするには、カスタムコンバータが必要です。ラッパーは以前は生成されていませんでしたが、オブジェクトが。を返すメソッドを提供する場合、オブジェクトをA::n()
変換できるというステートメントを続けましょう。A
get_num()
int
まず、A_from_python
コンバーターとコンストラクターの関数を提供する構造体を記述します。
struct A_from_python
{
static void* convertible( PyObject* obj_ptr )
{
// assume it is, for now...
return obj_ptr;
}
// Convert obj_ptr into an A instance
static void construct(
PyObject* obj_ptr,
boost::python::converter::rvalue_from_python_stage1_data* data)
{
std::cout << "constructing A from ";
PyObject_Print( obj_ptr, stdout, 0 );
std::cout << std::endl;
// Obtain a handle to the 'get_num' method on the python object.
// If it does not exists, then throw.
PyObject* n_ptr =
boost::python::expect_non_null(
PyObject_CallMethod( obj_ptr,
(char*)"get_num",
(char*)"()" ));
long n_val = 0;
n_val = PyInt_AsLong( n_ptr );
Py_DECREF( n_ptr );
// Grab pointer to memory into which to construct the new A
void* storage = (
(boost::python::converter::rvalue_from_python_storage< A >*)
data)->storage.bytes;
// in-place construct the new A using the data
// extracted from the python object
new ( storage ) A( n_val );
// Stash the memory chunk pointer for later use by boost.python
data->convertible = storage;
}
A_from_python()
{
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id< A >() );
}
};
boost::python::expect_non_null
NULL
が返された場合に例外をスローするために使用されます。これは、Pythonオブジェクトがメソッドを提供する必要があるというダックタイピングの保証を提供するのに役立ちますget_num
。PyObject
が特定のタイプのインスタンスであることがわかっている場合は、タイプを使用しboost::python::api::handle
て直接抽出することができ、インターフェイスboost::python::api::object
を介して一般的に呼び出しを行う必要がありません。PyObject
次に、コンバーターをモジュールに登録します。
using namespace boost::python;
BOOST_PYTHON_MODULE(example)
{
// register the from-python converter for A
A_from_python();
class_< A >( "A", init< long >() )
;
class_<B>( "B", init< A >() )
.def( "doSomething", &B::doSomething )
;
}
A
、、、B
または関連するラッパー定義に変更はありません。自動変換機能が作成され、モジュール内で定義/登録されました。
>>> from example import A, B
>>> a = A( 4 )
>>> b = B( a )
>>> b.doSomething()
8
>>> class D:
... def __init__( self, n ):
... self.n = n
... def get_num( self ):
... return self.n
...
>>> d = D( 5 )
>>> b = B( d )
constructing A from <__main__.D instance at 0xb7f7340c>
>>> b.doSomething()
10
>>> class E: pass
...
>>> e = E()
>>> b = B( e )
constructing A from <__main__.E instance at 0xb7f7520c>
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: get_num
D::get_num()
が存在するため、のコンストラクターに渡されるときA
のインスタンスから構築されます。ただし、は存在せず、のインスタンスから構築しようとすると例外が発生します。D
D
B
E::get_num()
A
E
代替の変換ソリューション。
C-APIを介したダックタイピングの実装は、タイプが大きくなると非常に複雑になる可能性があります。別の解決策は、Pythonでダックタイピングを実行し、Pythonファイルをライブラリと一緒に配布することです。
example_ext.py
A
とB
タイプ、およびモンキーパッチB
のコンストラクターをインポートします。
from example import A, B
def monkey_patch_B():
# Store handle to original init provided by Boost.
original_init = B.__init__
# Construct an A object via duck-typing.
def construct_A( obj ):
return A( obj.get_num() )
# Create a new init that will delegate to the original init.
def new_init( self, obj ):
# If obj is an instance of A, use it. Otherwise, construct
# an instance of A from object.
a = obj if isinstance( obj, A ) else construct_A ( obj )
# Delegate to the original init.
return original_init( self, a )
# Rebind the new_init.
B.__init__ = new_init
monkey_patch_B()
エンドユーザーに必要な唯一の変更は、次example_ext
の代わりにインポートすることですexample
。
>>> from example_ext import A, B
>>> a = A( 6 )
>>> b = B( a )
>>> b.doSomething()
12
>>> class D:
... def __init__( self, n ):
... self.n = n
... def get_num( self ):
... return self.n
...
>>> d = D( 7 )
>>> b = B( d )
>>> b.doSomething()
14
>>> class E: pass
...
>>> e = E()
>>> b = B( e )
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "example_ext.py", line 15, in new_init
a = obj if isinstance( obj, A ) else construct_A ( obj )
File "example_ext.py", line 9, in construct_A
return A( obj.get_num() )
AttributeError: E instance has no attribute 'get_num'
パッチが適用されたコンストラクターは、のインスタンスA
がに渡されることを保証するためB
、はA_from_python::construct
呼び出されません。したがって、出力に欠落しているprintステートメント。
このアプローチはC-APIを回避し、ダックタイピングの実行を容易にしますが、変換のためにAPIの一部に特別なパッチを適用する必要があるという大きなトレードオフがあります。一方、自動型変換機能を使用できる場合は、パッチを適用する必要はありません。
また、C ++とPythonの両方のアクセス制御は、偶発的な誤用から保護することを目的としています。どちらも、私的な可視性を持つメンバーへのアクセスを故意に取得することから保護しません。Pythonで行う方がはるかに簡単ですが、C ++標準では、明示的なテンプレートのインスタンス化によって特に許可されています。