dav*_*idA 8 c++ python boost 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;
};
Run Code Online (Sandbox Code Playgroud)
在包装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>());
}
};
Run Code Online (Sandbox Code Playgroud)
然后我注册这个:
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>())
;
}
Run Code Online (Sandbox Code Playgroud)
可转换和构造是回答"这是可转换的吗?"的方法.和"如何转换?" 问题分别.我观察到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()
Run Code Online (Sandbox Code Playgroud)
简而言之,将B包装器定义为:
class_<B>( "B", init< A >() )
Run Code Online (Sandbox Code Playgroud)
代替
class_<B>( "B", init< object >() )
Run Code Online (Sandbox Code Playgroud)
在Boost.Python中定义类的包装器时(至少在1.50中),class_模板会生成转换和构造函数.这允许A转换为A包装器并从其构造.这些PyObject转换具有严格的类型检查,并要求在python中执行以下操作:isinstance( obj, A ).
自定义转换器通常用于支持:
std::pair< long, long >为和PyTupleObject.Baccept类.DADB从一个实例构造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_;
};
Run Code Online (Sandbox Code Playgroud)
包装器将定义为:
using namespace boost::python;
BOOST_PYTHON_MODULE(example)
{
class_< A >( "A", init< long >() )
;
class_<B>( "B", init< A >() )
.def( "doSomething", &B::doSomething )
;
}
Run Code Online (Sandbox Code Playgroud)
B的包装器明确指出它将从一个A对象通过构造init< A >().此外,A由于没有为A::n()函数定义包装器,因此接口未完全暴露给Python对象.
>>> from example import A, B
>>> a = A( 1 )
>>> b = B( a )
>>> b.doSomething()
2
Run Code Online (Sandbox Code Playgroud)
这也适用于派生自的类型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
Run Code Online (Sandbox Code Playgroud)
但是,未启用鸭子输入.
>>> 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)
Run Code Online (Sandbox Code Playgroud)
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 >() );
}
};
Run Code Online (Sandbox Code Playgroud)
boost::python::expect_non_null如果NULL返回则用于抛出异常.这有助于提供python对象必须提供get_num方法的duck-typing保证.如果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 )
;
}
Run Code Online (Sandbox Code Playgroud)
没有对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
Run Code Online (Sandbox Code Playgroud)
D::get_num()存在,并且因此A,从实例构造D时D被传递给B的构造.但是,E::get_num()不存在,并且在尝试A从实例构造时引发异常E.
通过C-API实现duck-typing可能会因类型较大而变得非常复杂.另一种解决方案是在python中执行duck-typing,并将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()
Run Code Online (Sandbox Code Playgroud)
最终用户所需的唯一更改是导入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'
Run Code Online (Sandbox Code Playgroud)
由于修补的构造函数保证A将传递一个实例B,因此A_from_python::construct不会调用它.因此,输出中缺少打印语句.
虽然这种方法避免了C-API,使得更容易执行鸭子类型,但它确实有一个主要的权衡,因为它需要对API的某些部分进行特殊修补以进行转换.另一方面,当自动类型转换功能可用时,不需要修补.
此外,对于它的价值,C++和Python中的访问控制旨在防止意外滥用.既不能防止故意获取具有私人可见性的成员的访问权限.在Python中更容易做到,但是通过显式模板实例化在C++标准中特别允许.
| 归档时间: |
|
| 查看次数: |
2258 次 |
| 最近记录: |