C++/Python interfacing

Introduction

Boost.Python has a nice introduction to call policies. "Call policies concept" document will provide you with formal definition.

Syntax

The call policies in Py++ are named exactly as in Boost.Python, only the syntax is slightly different. For instance, this call policy:

return_internal_reference< 1, with_custodian_and_ward<1, 2> >()

becomes in Py++

return_internal_reference( 1, with_custodian_and_ward(1, 2) )

Py++ supports all call policies presented in Boost.Python.

Usage example

Every "callable" object in Py++ has call_policies property.

C++ code:

struct data{...};
const data& do_smth( const data& d, int x );

void return_second_arg( int x, int y );

typedef struct opaque_ *opaque_pointer;
opaque_pointer get_opaque();

Python code:

from pyplusplus import module_builder
from pyplusplus.module_builder import call_policies

mb = module_builder.module_builder_t( ... )
mb.free_function( 'return_second_arg' ).call_policies = call_policies.return_arg( 2 )
#---------------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

mb.member_function( 'do_smth' ).call_policies = call_policies.return_self()
#-------------------------------^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

mb.calldef( 'get_opaque' ).call_policies
    = call_policies.return_value_policy( call_policies.return_opaque_pointer )

Defaults

Py++ is able to "guess" few call policies, base on analysis of return type and\or operator name:

  • default_call_policies:

    • Python immutable type returned by value: C++ fundamental types, std::string, enumerations
    • user-defined type ( class ) returned by value
    • return type is const char*
  • return_value_policy

    • return_opaque_pointer

      • return type is void*

      • return type is const void*

      • return type is T* and T is a user defined opaque type

        class_t and class_declaration_t classes have opaque property. You have to set it to True, if you want Py++ to create this call policy automatically for all functions, that use T* as return type.

    • copy_const_reference

      • return type is const T&
      • for member operator[] that returns const reference to immutable type
    • return_by_value

      • return type is const wchar_t*
    • copy_non_const_reference

      • return type is T&, for member operator[] that returns reference to immutable type
    • return_internal_reference

      • return type is T&, for member operator[]
  • return_self

    This call policy will be used for operator=.

Missing call policies

If you don't specify call policy for a function and it needs one, few things will happen:

  • Py++ prints a warning message

  • Py++ generates code with

    /* undefined call policies */
    

    comment, in place of call policy. If Py++ was wrong and function doesn't need call policy the generate code will compile fine, otherwise you will get a compilation error.

Special case

Before you read this paragraph consider to read Boost.Python return_opaque_pointer documentation.

return_value_policy( return_opaque_pointer ) is a special policy for Boost.Python. In this case, it requires from you to define specialization for the boost::python::type_id function on the type pointed to by returned pointer. Py++ will generate the required code.

Actually you should define boost::python::type_id specialization also in case a function takes the opaque type as an argument. Py++ can do it for you, all you need is to say to mark a declaration as opaque.

Example:

struct identity_impl_t{};
typedef identity_impl_t* identity;

struct world_t{

    world_t( identity id );

    identity get_id() const;

    ...
};

Py++ code:

mb = module_builder_t(...)
mb.class_( 'identity_impl_t' ).opaque = True

Py++ defined call policies

custom_call_policies

custom_call_policies policies functionality was born to allow you to define your own call polices and use them with same level of convenience as built-in ones.

The usage is pretty simple:

from pyplusplus import module_builder
from pyplusplus.module_builder import call_policies

mb = module_builder.module_builder_t( ... )
mb.free_function( ... ).call_policies \
    = call_policies.custom_call_policies( your call policies code )

Optionally you can specify name of the header file, which should be included:
mb.free_function( ... ).call_policies \
   = call_policies.custom_call_policies( your call policies code, "xyz.hpp" )

return_pointee_value

Class return_pointee_value is a model of ResultConverterGenerator which can be used to wrap C++ functions returning any pointer, such that the pointee of return value is copied into a new Python object.

Example

float* get_value(){
    static float value = 0.5;
    return &value;
}

float* get_null_value(){
  return (float*)( 0 );
}

namespace bpl = boost::python;
BOOST_PYTHON_MODULE(my_module){
  def( "get_value"
       , bpl::return_value_policy< pyplusplus::call_policies::return_pointee_value<> >() );

  def( "get_null_value"
       , bpl::return_value_policy< pyplusplus::call_policies::return_pointee_value<> >() );
}

The Py++ code is not that different from what you already know:

from pyplusplus import module_builder
from pyplusplus.module_builder import call_policies

mb = module_builder.module_builder_t( ... )
mb.free_function( return_type='float *' ).call_policies \
    = call_policies.return_value_policy( call_policies.return_pointee_value )

Python code:

import my_module

assert 0.5 == my_module.get_value()
assert None is my_module.get_null_value()

as_tuple

Class as_tuple is a model of ResultConverterGenerator which can be used to wrap C++ functions returning a pointer to arrays with fixed size. The policy will construct a Python tuple from the array and treat the array memory.

Example

 struct vector3{
     ...

     float* clone_raw_data() const{
         float* values = new float[3];
         //copy values
         return values;
     }

     const flow* get_raw_data() const{
         return m_values;
     }

 private:
     float m_values[3];
 };

namespace bpl = boost::python;
namespace pypp_cp = pyplusplus::call_policies;
BOOST_PYTHON_MODULE(my_module){
  bpl::class_< vector3 >( "vector3" )
      .def( "clone_raw_data"
            , &::vector3::clone_raw_data
            , bpl::return_value_policy< pypp_cp::arrays::as_tuple< 3, pypp_cp::memory_managers::delete_ > >() )
      .def( "get_raw_data"
            , &::vector3::get_raw_data
            , bpl::return_value_policy< pypp_cp::arrays::as_tuple< 3, pypp_cp::memory_managers::none > >() ) );
}

as_tuple class

as_tuple is a template class that takes few arguments:

  1. the size of the array - compile time constant
  2. memory management policy - a class, which will manage the return value. There are two built-in memory managers:
    • delete_ - the array will be deleted after it was copied to tuple, using operator delete[]
    • none - do nothing

The Py++ code is slightly different from the C++ one, but it is definitely shorter:

from pyplusplus import module_builder
from pyplusplus.module_builder import call_policies

mb = module_builder.module_builder_t( ... )
mb.member_function( 'clone_raw_data' ).call_policies \
    = call_policies.convert_array_to_tuple( 3, call_policies.memory_managers.delete_ )
mb.member_function( 'get_raw_data' ).call_policies \
    = call_policies.convert_array_to_tuple( 3, call_policies.memory_managers.none )

return_range

Class return_range is a model of CallPolicies, which can be used to wrap C++ functions that return a pointer to some array. The new call policy constructs object, which provides a regular Python sequence interface.

Example

struct image_t{

    ...

    const unsigned char* get_data() const{
        return m_raw_data;
    }

    ssize_t get_width() const{
        return m_width;
    }

    ssize_t get_height() const{
        return m_height;
    }

private:
    unsigned long m_width;
    unsigned long m_height;
    unsigned char* m_raw_data;
};

Before introducing the whole solution, I would like to describe "return_range" interface.

return_range definition

template < typename TGetSize
           , typename TValueType
           , typename TValuePolicies=boost::python::default_call_policies >
struct return_range : boost::python::default_call_policies
{ ... };

Boost.Python call policies are stateless classes, which do not care any information about the invoked function or object. In out case we have to pass next information:

  • the size of array
  • array type
  • "__getitem__" call policies for array elements

TGetSize parameter

TGetSize is a class, which is responsible to find out the size of the returned array.

TGetSize class must have:

  • default constructor

  • call operator with next signature:

    ssize_t operator()( boost::python::tuple args );
    

    args is a tuple of arguments, the function was called with.

    Pay attention: this operator will be invoked after the function. This call policy is not thread-safe!

For our case, next class could be defined:

struct image_data_size_t{
    ssize_t operator()( boost::python::tuple args ){
        namespace bpl = boost::python;
        bpl::object self = args[0];
        image_t& img = bpl::extract< image_t& >( self );
        return img.get_width() * img.get_height();
    }
};

Passing all arguments, instead of single "self" argument gives you an ability to treat functions, where the user asked to get access to the part of the array.

struct image_t{
    ...
    const unsigned char* get_data(ssize_t offset) const{
        //check that offset represents a legal value
        ...
        return &m_raw_data[offset];
    }
    ...
};

Next "get size" class treats this situation:

struct image_data_size_t{
    ssize_t operator()( boost::python::tuple args ){
        namespace bpl = boost::python;
        bpl::object self = args[0];
        image_t& img = bpl::extract< image_t& >( self );
        bpl::object offset_obj = args[1];
        ssize_t offset = bpl::extract< ssize_t >( offset_obj );
        return img.get_width() * img.get_height() - offset;
    }
};

TValueType parameter

TValueType is a type of array element. In our case it is unsigned char.

TValuePolicies parameter

TValuePolicies is a "call policy" class, which will be applied when the array element is returned to Python. This is a call policy for "__getitem__" function.

unsigned char is mapped to immutable type in Python, so I have to use default_call_policies. default_call_policies is a default value for TValuePolicies parameter.

I think, now you are ready to see the whole solution:

namespace bpl = boost::python;
namespace ppc = pyplusplus::call_policies;

BOOST_PYTHON_MODULE(my_module){
  bpl::class_< image_t >( "image_t" )
      .def( "get_width", &image_t::get_width )
      .def( "get_height", &image_t::get_height )
      .def( "get_raw_data", ppc::return_range< image_size_t, unsigned char >() );
}

Py++ integration

The Py++ code is not that different from what you already know:

from pyplusplus import module_builder
from pyplusplus.module_builder import call_policies

image_size_code = \
"""
struct image_data_size_t{
    ssize_t operator()( boost::python::tuple args ){
        namespace bpl = boost::python;
        bpl::object self = args[0];
        image_t& img = bpl::extract< image_t& >( self );
        return img.get_width() * img.get_height();
    }
};
"""

mb = module_builder.module_builder_t( ... )
image = mb.class_( 'image_t' )
image.add_declaration_code( image_size_code )
get_raw_data = image.mem_fun( 'get_raw_data' )
get_raw_data.call_policies \
    = call_policies.return_range( get_raw_data, "image_data_size_t" )

call_policies.return_range arguments:

  1. A reference to function. Py++ will extract by itself the type of the array element.
  2. A name of "get size" class.
  3. A call policies for "__getitem__" function. Py++ will analyze the array element type. If the type is mapped to immutable type, than default_call_policies is used, otherwise you have to specify call policies.

Python usage code:

from my_module import *

img = image_t(...)
for p in img.get_raw_data():
    print p

Dependencies

The new call policy depends on new indexing suite and Py++ :-). But if you want you can extract the relevant piece of code from this file.



Valid HTML 4.01 Transitional Valid CSS! SourceForge.net Logo