gpt4 book ai didi

c++ - 如何使用 Boost.Python 将 C++ 对象传递给另一个 C++ 对象

转载 作者:太空狗 更新时间:2023-10-29 20:56:45 39 4
gpt4 key购买 nike

我有一些 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 的构造函数。我做了一些挖掘和 solution我发现是写一个“转换器”类:

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>())
;
}

Convertible 和construct 是回答“这是可转换的吗?”的方法。和“如何转换?”分别提问。我观察到construct() 方法非常重要——它必须进入A 的PyObject*,提取所有相关字段,然后重建一个C++ 实例,然后将其传递给B 的构造函数。因为 A 包含一些私有(private)字段,它必须通过公共(public)访问机制来做到这一点(而对于纯 Python 对象,它不必这样做,对吧?)。这似乎有效。

然而,'construct'函数中的字段提取真的有必要吗?好像很费劲。如果 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_模板生成转换和构造函数。这允许 A转换为 A的包装。这些 PyObject转换具有严格的类型检查,并且要求在 python 中满足以下条件: isinstance( obj, A ) .
自定义转换器通常用于支持:
  • 与现有 Python 类型之间的自动转换。例如,转换 std::pair< long, long >往返于 PyTupleObject .
  • 鸭打字。例如,有 B接受类(class)D ,这不是源自 A ,只要D提供兼容的接口(interface)。

  • 施工 B来自 A 的实例
    AB既不是现有的 Python 类型,也不需要鸭子类型,自定义转换器不是必需的。对于 BA 为例,它可以像指定 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的接口(interface)没有完全暴露给 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可以从提供兼容接口(interface)的对象构造,然后需要自定义转换器。尽管之前没有为 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()存在,因此 AD 的实例构造而成当 D传递给 B的构造函数。然而, E::get_num()不存在,并在尝试构造 A 时引发异常来自 E 的实例.

    另一种转换解决方案。
    对于较大的类型,通过 C-API 实现鸭子类型可能会变得非常复杂。另一种解决方案是在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 的一部分。另一方面,当自动类型转换功能可用时,不需要修补。

    此外,就其值(value)而言,C++ 和 Python 中的访问控制旨在防止意外误用。也不能防止故意访问具有私有(private)可见性的成员。在 Python 中这样做要容易得多,但在 C++ 标准中通过显式模板实例化特别允许这样做。

    关于c++ - 如何使用 Boost.Python 将 C++ 对象传递给另一个 C++ 对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11926340/

    39 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com