簡體   English   中英

如何使用Boost.Python將C ++對象傳遞給另一個C ++對象

[英]How to pass a C++ object to another C++ object with Boost.Python

我有一些C ++代碼定義了兩個類,A和B.B在構造期間采用A的實例。 我用Boost.Python包裝了A,以便Python可以創建A的實例以及子類。 我想和B做同樣的事情。

class A {
    public:
        A(long n, long x, long y) : _n(n), _x(x), _y(y) {};
        long get_n() { return _n; }
        long get_x() { return _x; }
        long get_y() { return _y; }
    private:
        long _n, _x, _y;
};

class B {
    public:
        B(A a) : _a(a) {};
        doSomething() { ... };
    private:
        A _a;
};

在包裝B時,我需要弄清楚如何將A的實例傳遞給B的構造函數。 我做了一些挖掘,我找到的解決方案是寫一個“轉換器”類:

struct A_from_python_A {
    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) {
        // extract 'n':
        PyObject * n_ptr = PyObject_CallMethod(obj_ptr, (char*)"get_n", (char*)"()");
        long n_val = 0;
        if (n_ptr == NULL) {
            cout << "... an exception occurred (get_n) ..." << endl;
        } else {
            n_val = PyInt_AsLong(n_ptr);
            Py_DECREF(n_ptr);
        }

        // [snip] - also do the same for x, y

        // 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, x_val, y_val);

        // Stash the memory chunk pointer for later use by boost.python
        data->convertible = storage;
    }

    // register converter functions
    A_from_python_A() {
        boost::python::converter::registry::push_back(
            &convertible,
            &construct,
            boost::python::type_id<A>());
    }
};

然后我注冊這個:

BOOST_PYTHON_MODULE(interpolation_ext)
{
    // register the from-python converter for A
    A_from_python_A();

    class_<A>("A", init<long, long, long>())
        ;

    class_<B>("B", init<object>())
        ;
}

可轉換和構造是回答“這是可轉換的嗎?”的方法。 和“如何轉換?” 問題分別。 我觀察到construct()方法是非平凡的 - 它必須到達A的PyObject *,提取所有相關字段,然后重建一個C ++實例,然后傳遞給B的構造函數。 因為A包含一些私有字段,所以它必須通過公共訪問機制來做到這一點(而對於純Python對象,它不必,對吧?)。 這似乎有效。

但是, '構造'函數中的字段提取真的是必要的嗎? 這似乎很費勁。 如果A是復合對象,它可能變得非常復雜,並且可能需要一個轉換器來調用另一個轉換器。 我可能理解A是一個Python類的要求,但如果A實例來自C ++端,是否有辦法確定是這種情況,然后只需獲得一個句柄(例如指針)到這個'native'對象,作為捷徑?

這是相關的python代碼:

from my_ext import A, B
a = A(1,2,3)
b = B(a)
b.doSomething()

簡而言之,將B的包裝定義為:

class_<B>( "B", init< A >() )

代替

class_<B>( "B", init< object >() )

在Boost.Python中定義類的包裝器時(至少在1.50中), class_ template生成轉換和構造函數。 這允許A轉換為A的包裝並從其構造。 這些PyObject轉換具有嚴格的類型檢查,並且要求在python中使用以下內容: isinstance( obj, A )

自定義轉換器通常用於支持:

  • 與現有Python類型的自動轉換。 例如,將std::pair< long, long >轉換為PyTupleObject和從PyTupleObject
  • 鴨打字。 例如,只要D提供兼容的接口,讓B接受D類,它不是從A派生的。

A的實例構造B

由於AB既不是現有的Python類型,也不需要鴨子類型,因此不需要自定義轉換器。 對於B來取A的實例,它可以像指定init采用A一樣簡單。

這是AB的簡化示例,其中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的包裝器明確指出它將通過init< A >()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)

從可轉換為A的對象構造B

為了支持可以從提供兼容接口的對象構造B的情況,需要自定義轉換器。 雖然先前沒有為A::n()生成包裝器, A如果對象提供返回intget_num()方法,則繼續使用可以將對象轉換為A的語句。

首先,編寫一個提供轉換器和構造函數的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方法的duck-typing保證。 如果已知PyObject是給定類型的實例,那么可以使用boost::python::api::handleboost::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 )
    ;
}

AB或其關聯的包裝器定義未發生任何更改。 創建了自動轉換功能,然后在模塊中定義/注冊。

>>> 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()存在,因此當D傳遞給B的構造函數時, A是從D的實例構造的。 但是, E::get_num()不存在,並且在嘗試從E的實例構造A時會引發異常。


另一種轉換解決方案。

通過C-API實現duck-typing可能會因類型較大而變得非常復雜。 另一種解決方案是在python中執行duck-typing,並將python文件與庫一起分發。

example_ext.py將導入AB類型,以及猴子補丁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 因此,輸出中缺少打印語句。

雖然這種方法避免了C-API,使得更容易執行鴨子類型,但它確實有一個主要的權衡,因為它需要對API的某些部分進行特殊修補以進行轉換。 另一方面,當自動類型轉換功能可用時,不需要修補。


此外,對於它的價值,C ++和Python中的訪問控制旨在防止意外濫用。 既不能防止故意獲取具有私人可見性的成員的訪問權限。 在Python中更容易做,但是通過顯式模板實例化在C ++標准中特別允許。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM