Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    primarykey
    data
    text
    <p>It is possible with <code>__getattr__</code> and custom <code>%MethodCode</code>; however, there are a few points to take into consideration:</p> <ul> <li>An intermediate type/object needs to be created, as <code>a.x</code> will return an object that provides <code>__getitem__</code> and <code>__setitem__</code>. Both methods should raise an <code>IndexError</code> when out of bounds occurs, as this is part of the old protocol used to iterate via <code>__getitem__</code>; without it, a crash would occur when iterating over <code>a.x</code>.</li> <li><p>In order to guarantee the lifetime of the vector, the <code>a.x</code> object needs to maintain a reference to the object that owns the vector (<code>a</code>). Consider the following code:</p> <pre><code>a = A() x = a.x a = None # If 'x' has a reference to 'a.v' and not 'a', then it may have a # dangling reference, as 'a' is refcounted by python, and 'a.v' is # not refcounted. </code></pre></li> <li><p>Writing <code>%MethodCode</code> can be difficult, especially when having to manage the reference counting during error cases. It requires an understanding of the python C API and SIP.</p></li> </ul> <p>For an alternative solution, consider:</p> <ul> <li>Design the python bindings to provide functionality.</li> <li>Design class(es) in python to provide the pythonic interface that uses the bindings.</li> </ul> <p>While the approach has a few drawbacks, such as the code is separated into more files that may need to be distributed with the library, it does provide some major benefits:</p> <ul> <li>It is much easier to implement a pythonic interface in python than in C or the interoperability library's interface.</li> <li>Support for slicing, iterators, etc. can be more naturally implemented in python, instead of having to manage it through the C API.</li> <li>Can leverage python's garbage collector to manage the lifetime of the underlying memory.</li> <li>The pythonic interface is decoupled from whatever implementation is being used to provide interoperability between python and C++. With a flatter and simpler binding interface, changing between implementations, such as Boost.Python and SIP, is much easier.</li> </ul> <hr> <p>Here is an walk-through demonstrating this approach. First, we start with the basic <code>A</code> class. In this example, I have provided a constructor that will set some initial data.</p> <p><code>a.hpp</code>:</p> <pre><code>#ifndef A_HPP #define A_HPP #include &lt;vector&gt; class A { std::vector&lt; double &gt; v; public: A() { for ( int i = 0; i &lt; 6; ++i ) v.push_back( i ); } double&amp; x( int i ) { return v[2*i]; } double x( int i ) const { return v[2*i]; } double&amp; y( int i ) { return v[2*i+1]; } double y( int i ) const { return v[2*i+1]; } std::size_t size() const { return v.size() / 2; } }; #endif // A_HPP </code></pre> <p>Before doing the bindings, lets examine the <code>A</code> interface. While it is an easy interface to use in C++, it has some difficulties in python:</p> <ul> <li>Python does not support overloaded methods, and idioms to support overloading will fail when the argument type/counts are the same.</li> <li>The concept of a reference to a double (float in Python) is different between the two languages. In Python, the float is an immutable type, so its value cannot be changed. For example, in Python the statement <code>n = a.x[0]</code> binds <code>n</code> to reference the <code>float</code> object returned from <code>a.x[0]</code>. The assignment <code>n = 4</code> rebinds <code>n</code> to reference the <code>int(4)</code> object; it does not set <code>a.x[0]</code> to <code>4</code>.</li> <li><code>__len__</code> expects <code>int</code>, not <code>std::size_t</code>.</li> </ul> <p>Lets create a basic intermediate class that will help simplify the bindings.</p> <p><code>pya.hpp</code>:</p> <pre><code>#ifndef PYA_HPP #define PYA_HPP #include "a.hpp" struct PyA: A { double get_x( int i ) { return x( i ); } void set_x( int i, double v ) { x( i ) = v; } double get_y( int i ) { return y( i ); } void set_y( int i, double v ) { y( i ) = v; } int length() { return size(); } }; #endif // PYA_HPP </code></pre> <p>Great! <code>PyA</code> now provides member functions that do not return references, and length is returned as an <code>int</code>. It is not the best of interfaces, the bindings are being designed to provide the needed <em>functionality</em>, rather than the desired <em>interface</em>.</p> <p>Now, lets write some simple bindings that will create class <code>A</code> in the <code>cexample</code> module.</p> <p>Here is the bindings in SIP:</p> <pre><code>%Module cexample class PyA /PyName=A/ { %TypeHeaderCode #include "pya.hpp" %End public: double get_x( int ); void set_x( int, double ); double get_y( int ); void set_y( int, double ); int __len__(); %MethodCode sipRes = sipCpp-&gt;length(); %End }; </code></pre> <p>Or if you prefer Boost.Python:</p> <pre><code>#include "pya.hpp" #include &lt;boost/python.hpp&gt; BOOST_PYTHON_MODULE(cexample) { using namespace boost::python; class_&lt; PyA &gt;( "A" ) .def( "get_x", &amp;PyA::get_x ) .def( "set_x", &amp;PyA::set_x ) .def( "get_y", &amp;PyA::get_y ) .def( "set_y", &amp;PyA::set_y ) .def( "__len__", &amp;PyA::length ) ; } </code></pre> <p>Due to the <code>PyA</code> intermediate class, both of the bindings are fairly simple. Additionally, this approach requires less SIP and Python C API knowledge, as it requires less code within <code>%MethodCode</code> blocks.</p> <p>Finally, create <code>example.py</code> that will provide the desired pythonic interface:</p> <pre><code>class A: class __Helper: def __init__( self, data, getter, setter ): self.__data = data self.__getter = getter self.__setter = setter def __getitem__( self, index ): if len( self ) &lt;= index: raise IndexError( "index out of range" ) return self.__getter( index ) def __setitem__( self, index, value ): if len( self ) &lt;= index: raise IndexError( "index out of range" ) self.__setter( index, value ) def __len__( self ): return len( self.__data ) def __init__( self ): import cexample a = cexample.A() self.x = A.__Helper( a, a.get_x, a.set_x ) self.y = A.__Helper( a, a.get_y, a.set_y ) </code></pre> <p>In the end, the bindings provide the <em>functionality</em> we need, and python creates the <em>interface</em> we want. It is possible to have the bindings provide the interface; however, this can require a rich understanding of the differences between the two languages and the binding implementation.</p> <pre>>>> from example import A >>> a = A() >>> for x in a.x: ... print x ... 0.0 2.0 4.0 >>> a.x[0] = 4 >>> for x in a.x: ... print x ... 4.0 2.0 4.0 >>> x = a.x >>> a = None >>> print x[0] 4.0</pre>
    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.
    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