Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>In short, define <code>B</code>'s wrapper as:</p> <pre><code>class_&lt;B&gt;( "B", init&lt; A &gt;() ) </code></pre> <p>instead of</p> <pre><code>class_&lt;B&gt;( "B", init&lt; object &gt;() ) </code></pre> <p>When defining a wrapper for class in Boost.Python (at least in 1.50), the <code>class_</code> template generates convert and construct functions. This allows <code>A</code> to be converted to and constructed from a <code>A</code>'s wrapper. These <code>PyObject</code> conversions have strict type-checking, and require that the following be true in python: <code>isinstance( obj, A )</code>.</p> <p>Custom converters are often used to support:</p> <ul> <li>Automatic conversions to and from existing Python types. For example, converting <code>std::pair&lt; long, long &gt;</code> to and from a <code>PyTupleObject</code>.</li> <li>Duck-typing. For example, having <code>B</code> accept class <code>D</code>, which is not derived from <code>A</code>, as long as <code>D</code> provides a compatible interface.</li> </ul> <hr> <h3>Constructing <code>B</code> from an instance of <code>A</code></h3> <p>Since <code>A</code> and <code>B</code> are neither existing Python types nor is duck-typing required, custom converters are not necessary. For <code>B</code> to take an instance of <code>A</code>, it can be as simple as specifying that <code>init</code> takes an <code>A</code>.</p> <p>Here is a simplified example of <code>A</code> and <code>B</code>, where <code>B</code> can be constructed from an <code>A</code>.</p> <pre><code>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_; }; </code></pre> <p>And the wrappers would be defined as:</p> <pre><code>using namespace boost::python; BOOST_PYTHON_MODULE(example) { class_&lt; A &gt;( "A", init&lt; long &gt;() ) ; class_&lt;B&gt;( "B", init&lt; A &gt;() ) .def( "doSomething", &amp;B::doSomething ) ; } </code></pre> <p><code>B</code>'s wrapper explicitly indicates that it will be constructed from an <code>A</code> object via <code>init&lt; A &gt;()</code>. Also, <code>A</code>'s interface is not fully exposed to the Python objects, as no wrapper was defined for the <code>A::n()</code> function.</p> <pre><code>&gt;&gt;&gt; from example import A, B &gt;&gt;&gt; a = A( 1 ) &gt;&gt;&gt; b = B( a ) &gt;&gt;&gt; b.doSomething() 2 </code></pre> <p>This also works for types that are derived from <code>A</code>. For example:</p> <pre><code>&gt;&gt;&gt; from example import A, B &gt;&gt;&gt; class C( A ): ... def __init__( self, n ): ... A.__init__( self, n ) ... &gt;&gt;&gt; c = C( 2 ) &gt;&gt;&gt; b = B( c ) &gt;&gt;&gt; b.doSomething() 4 </code></pre> <p>However, duck-typing is not enabled.</p> <pre><code>&gt;&gt;&gt; from example import A, B &gt;&gt;&gt; class E: pass ... &gt;&gt;&gt; e = E() &gt;&gt;&gt; b = B( e ) Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; Boost.Python.ArgumentError: Python argument types in B.__init__(B, instance) did not match C++ signature: __init__(_object*, A) </code></pre> <hr> <h3>Constructing <code>B</code> from an object that is convertible to <code>A</code>.</h3> <p>To support the case where <code>B</code> can be constructed from an object that provides a compatible interface, then custom converters are required. Although wrappers were not previously generated for <code>A::n()</code>, lets continue with the statement that an object can be converted to <code>A</code> if the object provides a <code>get_num()</code> method that returns an <code>int</code>.</p> <p>First, write an <code>A_from_python</code> struct that provides converter and constructors functions.</p> <pre><code>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 &lt;&lt; "constructing A from "; PyObject_Print( obj_ptr, stdout, 0 ); std::cout &lt;&lt; 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&lt; A &gt;*) data)-&gt;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-&gt;convertible = storage; } A_from_python() { boost::python::converter::registry::push_back( &amp;convertible, &amp;construct, boost::python::type_id&lt; A &gt;() ); } }; </code></pre> <p><code>boost::python::expect_non_null</code> is used to throw an exception if <code>NULL</code> is returned. This helps provide the duck-typing guarantee that the python object must provide a <code>get_num</code> method. If the <code>PyObject</code> is known to be an instance of given type, then it is possible to use <a href="http://www.boost.org/doc/libs/1_50_0/libs/python/doc/v2/handle.html"><code>boost::python::api::handle</code></a> and <a href="http://www.boost.org/doc/libs/1_50_0/libs/python/doc/v2/object.html"><code>boost::python::api::object</code></a> to directly extract the type, and avoid having to generically make calls through the <code>PyObject</code> interface.</p> <p>Next, register the converter in the module.</p> <pre><code>using namespace boost::python; BOOST_PYTHON_MODULE(example) { // register the from-python converter for A A_from_python(); class_&lt; A &gt;( "A", init&lt; long &gt;() ) ; class_&lt;B&gt;( "B", init&lt; A &gt;() ) .def( "doSomething", &amp;B::doSomething ) ; } </code></pre> <p>No changes have occurred to <code>A</code>, <code>B</code>, or their associated wrapper definitions. The auto-conversion functions were created, and then defined/registered within the module.</p> <pre><code>&gt;&gt;&gt; from example import A, B &gt;&gt;&gt; a = A( 4 ) &gt;&gt;&gt; b = B( a ) &gt;&gt;&gt; b.doSomething() 8 &gt;&gt;&gt; class D: ... def __init__( self, n ): ... self.n = n ... def get_num( self ): ... return self.n ... &gt;&gt;&gt; d = D( 5 ) &gt;&gt;&gt; b = B( d ) constructing A from &lt;__main__.D instance at 0xb7f7340c&gt; &gt;&gt;&gt; b.doSomething() 10 &gt;&gt;&gt; class E: pass ... &gt;&gt;&gt; e = E() &gt;&gt;&gt; b = B( e ) constructing A from &lt;__main__.E instance at 0xb7f7520c&gt; Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; AttributeError: get_num </code></pre> <p><code>D::get_num()</code> exists, and thus <code>A</code> is constructed from an instance of <code>D</code> when <code>D</code> is passed to <code>B</code>'s constructor. However, <code>E::get_num()</code> does not exists, and an exception is raised when trying to construct <code>A</code> from an instance of <code>E</code>.</p> <hr> <h3>An alternative conversion solution.</h3> <p>Implementing duck-typing via the C-API can become very complicated with larger types. An alternative solution is perform the duck-typing in python, and distribute the python file alongside the library.</p> <p><code>example_ext.py</code> will import the <code>A</code> and <code>B</code> types, as well as monkey patch <code>B</code>'s constructor:</p> <pre><code>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() </code></pre> <p>The only change required to the end-user is to import <code>example_ext</code> instead of <code>example</code>:</p> <pre><code>&gt;&gt;&gt; from example_ext import A, B &gt;&gt;&gt; a = A( 6 ) &gt;&gt;&gt; b = B( a ) &gt;&gt;&gt; b.doSomething() 12 &gt;&gt;&gt; class D: ... def __init__( self, n ): ... self.n = n ... def get_num( self ): ... return self.n ... &gt;&gt;&gt; d = D( 7 ) &gt;&gt;&gt; b = B( d ) &gt;&gt;&gt; b.doSomething() 14 &gt;&gt;&gt; class E: pass ... &gt;&gt;&gt; e = E() &gt;&gt;&gt; b = B( e ) Traceback (most recent call last): File "&lt;stdin&gt;", line 1, in &lt;module&gt; 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' </code></pre> <p>Since the patched constructor guarantees an instance of <code>A</code> will be passed to <code>B</code>, the <code>A_from_python::construct</code> will not get invoked. Hence, the missing print statements in the output.</p> <p>While this approach avoids the C-API, making it easier to perform duck-typing, it does have one major trade-off in that it requires parts of the API to be specifically patched for conversions. On the other hand, no patching is required when auto-type conversion functions are available.</p> <hr> <p>Also, for what it is worth, access control in both C++ and Python is intended to protect against accidental misuse. Neither protect against deliberately obtaining access to members with private visibility. It is much easier to do in Python, but it is specifically permitted in the C++ Standard through explicit template instantiations.</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. VO
      singulars
      1. This table or related slice is empty.
    2. VO
      singulars
      1. This table or related slice is empty.
    3. VO
      singulars
      1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload