Boost: encapsulate c + + classes into python classes

Posted by anna_cm on Sun, 09 Jan 2022 09:32:05 +0100

1. Description

This note is used to explain in detail how to convert classes in c + + into classes that can be used directly in the python environment.

2. Examples

A simple c + + class RealWorld is defined here, including public, private members and public member functions. In this example, we will show how to convert the member functions and member variables of the class into objects in python.

2.1 overall code

The code composition is as follows: classes HPP / CPP contains the definition and implementation of classes Py is a Python test file, cmakelists Txt is the build file.

02_ExposingClass$ tree
├── classes.cpp
├── classes.hpp
├── classes.py
└── CMakeLists.txt

2.2 classes.hpp

#include <string>

class RealWorld
{
    public:
        RealWorld(std::string n, char sex) : name(n), sex('m'), age(0.0){};
        std::string name;

        void Welcome();
        void SetAge(int age); 
        int GetAge();
        std::string GetName();
        char GetSex();

    private:
        char sex;
        int age;
};

2.3 classes.cpp

#include <iostream>

#include <boost/python.hpp>

#include "classes.hpp"

namespace python = boost::python;

void RealWorld::Welcome()
{
    std::cout << "Welcome to real world" << std::endl;
}

int RealWorld::GetAge()
{
    return age;
}

void RealWorld::SetAge(int value)
{
    age = value;
}

std::string RealWorld::GetName()
{
    return name;
}

char RealWorld::GetSex()
{
    return sex;
}

// Convert to classes module
BOOST_PYTHON_MODULE(classes)
{
    python::class_<RealWorld> ("RealWorld", python::init<std::string, char>())
        // Expose functions
        .def ("Welcome", &RealWorld::Welcome)
        .def ("GetAge", &RealWorld::GetAge)
        .def ("SetAge", &RealWorld::SetAge, python::args("value"))
        .def ("GetName", &RealWorld::GetName)
        .def ("GetSex", &RealWorld::GetSex)

        // Expose member
        .def_readwrite("name", &RealWorld::name)
        .add_property("age", &RealWorld::GetAge, &RealWorld::SetAge)
        .add_property("sex", &RealWorld::GetSex)
    ;
}

The public member variable in c + + class corresponds to a readable and writable variable in python

2.3

set(MODULE_NAME  classes)

include_directories(${CMAKE_SOURCE_DIR})

add_library(${MODULE_NAME} SHARED
	classes.cpp
	)

if (UNIX)
  set_target_properties(${MODULE_NAME}
    PROPERTIES
    PREFIX ""
  )
elseif (WIN32)
  set_target_properties(${MODULE_NAME}
  PROPERTIES
  SUFFIX ".pyd"
  )
endif()

target_link_libraries(${MODULE_NAME}
  ${Boost_LIBRARIES}
  ${PYTHON_LIBRARIES}
)

2.5 classes.py

#!/usr/bin/env python

import classes

t1 = classes.RealWorld("Xiangdi", 'm')

t1.Welcome()
t1.SetAge(20)
print (t1.name, "'s age is ", t1.age, "sex is ", t1.sex)

t1.name = "Xiaoming"
# t1.sex = 'f'                # sex has no set function, so can't be setted
t1.age = 25
print (t1.name, "'s age is ", t1.age, "sex is ", t1.sex)

2.6 compilation and operation

Cmakelists at the upper level Txt file contains the current directory

ADD_SUBDIRECTORY(02_ExposingClass)

compile

cd boost
cmake ..
make

function

cd build/lib
cp ../../02_ExposingClass/classes.py .
python classes.py

Welcome to real world
Xiangdi 's age is  20 sex is  m
Xiaoming 's age is  25 sex is  m


3. Expose class to python

There are usually two ways to convert c + + classes into python object s

class A { ... };
BOOST_PYTHON_MODULE(example)
{
  class_<A>("A");
}

The other is to create instances of c + + classes directly in python module

BOOST_PYTHON_MODULE(example1)
{
  object class_a = class_<A>("A");

  object instance_a = class_a();
}

Transform abstract class

Boost.Python will try to register a converter to handle wrapped functions, which handle the return value of class type functions, that is, by default, it must be able to copy the instance of C + + class construction to the object store that can be managed by python. For abstract classes, they will not be instantiated, so you need to tell boost Python cannot be copied.

class_< Abstract , boost::noncopyable>("Abstract", no_init);

The Abstract here corresponds to the C + + class name through no_init keyword to declare that this class cannot be copied.

Summary of construction methods

Boost.Python allows you to specify how Python objects will save the C + + objects they wrap. You can specify that they are shared_ PTR < T > (or any other smart pointer) is saved, in which case the library will be shared_ PTR < T > generates a converter to / from python. to_ The python converter will simply revolve around shared_ PTR < > build a new Python object. You can specify your C + + objects by shared_ PTR < U > hold. This allows you to hold a U object for scheduling, but still pass shared in your C + + code_ ptrs.

If you have a virtual function that you want to override in Python, you actually have to use the derived class U to save the T object, which overrides the virtual function to dispatch back to python. In this case, class U naturally must have access to Python objects

There are several problems with the above arrangement, but the most important one is if you let shared_ PTR < U > lives longer than its corresponding Python object, calls to Python overridable virtual functions will crash because they will try to call through invalid pointers.

class_<A>("A")
    .def(init<int, int>())
    .def(...)
    ;

class_<B>("B", init<int, int>())
    .def(...)
    ;

class_<C>("C", "C's docstring", init<int, int>())
    .def(...)
    ;

class_<D>("D", "D's docstring", init<int, int>(), "__init__ doc")
    .def(...) 
    ;


class_<E>("E")
    .def(...)
    ;

class_<F>("F", no_init)
    .def(...)
    ;

class_<G>("G", "G's docstring", no_init)
    .def(...) 
    ;

class_<H>("H", "H's docstring")
    .def(...) 
    ;
  • Init < int, int > () represents the parameters of the constructor of the c + + class, which corresponds to the constructor of the class under python__ init__ Function can set the default value in ();
  • no_init indicates that there is no constructor, so the corresponding python class will not have a constructor__ init__ ()
  • Other "doc/docstring" indicates description information

4. class_ Class explanation

class_ Is a template class defined in boost / Python / class In HPP file

4.1 class_ definition

// This is the primary mechanism through which users will expose
// C++ classes to Python.
template <
    class W // class being wrapped
    , class X1 // = detail::not_specified
    , class X2 // = detail::not_specified
    , class X3 // = detail::not_specified
    >
class class_ : public objects::class_base
{
 public: // types
    typedef objects::class_base base;
    typedef class_<W,X1,X2,X3> self;
    typedef typename objects::class_metadata<W,X1,X2,X3> metadata;
    typedef W wrapped_type;
    ...
}

Create a python class associated with the c + + type passed as its first argument. Although it has four template parameters, only the first is required (that is, W), which represents the c + + class to be encapsulated. The last three parameters are optional (X1/X2/X3) and can be provided in any order; Boost.Python determines the role of the parameter based on the type of the parameter.

It should be noted that X1/X2/X3 must have the following types of parameters:

parameterexplain
BaseA specialization of bases <... > that specifies W's previously exposed C + + base class.
HeldTypeMust be w, a class derived from W, or a dereference type with pointer:: type W, or a class derived from W.
Specifies when calling T's constructor or when ptr, ref, or call policies are not used
NonCopyableProhibit automatic registration and copying of W instances_ Python conversion. Required when w has no publicly accessible copy constructor. If yes, it must be boost::noncopyable

4.2 constructor

// Constructors with default __init__
class_(char const* name);
class_(char const* name, char const* docstring);

// Constructors, specifying non-default __init__
template <class Init>
class_(char const* name, Init);
template <class Init>
class_(char const* name, char const* docstring, Init);

You can see class_ A variety of constructors are provided. Except that name is essential, others can be defaulted. There is still a lot of room for users to play freely. It should be noted that if in class_ When instantiating, there is no explicit identification of "no_init", which does not mean that the class does not have a constructor or init(), but the constructor does not need parameters. Therefore, for classes without constructors (such as abstract classes), it is necessary to explicitly identify "no_init".

In fact, in addition to the list given on the official website, there are other construction methods to call. See the boost source code for details.

4.2 encapsulation member exchange function

class_ The member function of c + + class encapsulates the member function or non member function in c + + class through the def() function. It also provides a variety of overload types to choose from.

// Exposing additional __init__ functions
template <class Init>
class_& def(Init);

// defining methods
template <class F>
class_& def(char const* name, F f);
template <class Fn, class A1>
class_& def(char const* name, Fn fn, A1 const&);
template <class Fn, class A1, class A2>
class_& def(char const* name, Fn fn, A1 const&, A2 const&);
template <class Fn, class A1, class A2, class A3>
class_& def(char const* name, Fn fn, A1 const&, A2 const&, A3 const&);

class_& def(Init); Is a fixed way of use, so that the constructor of the class can be encapsulated independently.

Name represents the function name encapsulated in python, F/Fn is the corresponding c + + function name, and A1/A2/A3 represents attributes, which can correspond to docstring/policies/keywords respectively. These three can appear in any number and order. In boost In python, the parameter args() is included, and the return value type is return_value_policy() and so on appear in the form of class / obj.

A1-A3 respectively correspond to the contents of the following table:

nameattributeexplain
docstringAny ntbsThe value will be bound to the python method__ doc__ Attribute
policiesCallPolicies modelEncapsulation strategy of function results
keywordsparameterUsed to represent function parameters

4.3 encapsulating member variables

The member variables here correspond to the member variables in c + + classes. They are contents that can be accessed directly externally. They can be encapsulated into read-only / read-write types according to different attributes.

// exposing data members
template <class D>
class_& def_readonly(char const* name, D T::*pm);

template <class D>
class_& def_readwrite(char const* name, D T::*pm);

// exposing static data members
template <class D>
class_& def_readonly(char const* name, D const& d);
template <class D>
class_& def_readwrite(char const* name, D& d);
  • Name: variable name in python class after encapsulation;
  • d: The variable corresponding to the c + + to be encapsulated

4.4 creating attributes

The properties here correspond to private or protected variables in c + + classes. Such variables cannot be accessed directly from the outside, but only through the access interface in the class.

self& add_property(char const* name, Get fget, char const* docstr = 0)
self& add_property(char const* name, Get fget, Set fset, char const* docstr = 0)

// Add static member variable
self& add_static_property(char const* name, Get fget)
self& add_static_property(char const* name, Get fget, Set fset)

When adding private variables, read and set methods are followed. Note that at least a read interface (Get) needs to be provided.

Create a new Python attribute class instance, pass the object (fget) (and the second form of object(fset)) with the (optional) document string doc to its constructor, and then add the attribute to the python class object under construction with the given attribute name.

4.5 declaring static functions

class_& staticmethod(char const* name);

Declare the function specified by name as a static function under python, which is equivalent to a python statement:

setattr(self, name, staticmethod(getattr(self, name)))


5. Summary

Only some class es are shown here_ The methods provided are enough for most scenarios (I guess), and there are some other interfaces. We will analyze them in detail when we need to use them later. We won't guess here.

reference material

class_<> statement constructs python class object.
boost/python/class.hpp

Topics: Python C++ boost