current position:Home>Boost (3): encapsulate C + + classes into Python classes

Boost (3): encapsulate C + + classes into Python classes

2022-05-15 06:01:37Xiangdi

1. explain

This note is used to explain in detail how to c++ Classes in are converted to classes in python Classes that can be used directly by the environment .

2. Example

Here is a simple definition c++ class RealWorld, contain 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 python The objects within the .

2.1 The overall code

The code is composed as follows ,classes.hpp/cpp Contains the definition and implementation of classes ,classes.py by Python The 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)
    ;
}

c++ Class public Member variables , Corresponding to python Inside is a readable and writable

2.4 CMakeLists.txt

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 Compile operation

At a higher level CMakeLists.txt The 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 Methods

There are usually two ways to c++ Class to python Of object

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

The other is directly in python module Created in c++ Class

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

  object instance_a = class_a();
}

Convert abstract classes

Boost.Python Will try to register a converter to handle wrapped function , These functions handle class Function return value of type , In other words, by default, it must be possible to C++ An instance of a class construct is copied to an instance that can be constructed by python Managed object storage . And for abstract classes , Itself will not be instantiated , Then you need the user to tell Boost.Python This class cannot be copied .

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

there Abstract The corresponding is C++ Class name , adopt no_init Keyword to declare that this class cannot be copied .

Construction method summary

Boost.Python Allow you to specify Python How objects will save their wrapped C++ object . You can specify that they are by shared_ptr< T >( Or any other smart pointer ) preservation , under these circumstances , The library will be shared_ptr< T > Generate to / from Python Converter . to_python The converter will simply surround shared_ptr< > Building a new Python object . You can specify your C++ Object by shared_ptr< U > hold . This allows you to hold a for scheduling U object , But still in your C++ Pass... In code shared_ptrs.

If you want to be in Python Virtual functions covered in , You actually have to use derived classes U To preserve T object , It overrides the virtual function to dispatch back Python. under these circumstances , class U Nature must have access to Python object

There are several problems with the above arrangement , But the most important question is , If you allow shared_ptr< U > Than its corresponding Python Objects live longer , On the other hand Python Calls to overridable virtual functions will crash , Because they will try to call through an invalid pointer .

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>() Express c++ Arguments to the constructor of class , Corresponding to python The following kind of __init__ The function can be in () The default value is within ;
  • no_init Indicates that there is no constructor , So the corresponding python Class will not have __init__()
  • Other "doc/docstring" Represents descriptive information

4. class_ Class explanation

class_ It's a template class , It's defined in boost/python/class.hpp In 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 connection with the... Passed as its first parameter C++ Type associated Python class . Although it has four template parameters , But only the first one is necessary ( namely W), It represents the... To be encapsulated c++ class . The last three parameters are optional (X1/X2/X3), Can actually be provided in any order ; Boost.Python Determine the role of the parameter according to the type of the parameter .

It should be noted that ,X1/X2/X3 The following types of parameters must be used :

Parameters explain
Basebases<…> A specialization of , It designates W Previously disclosed C++ Base class .
HeldType Must be W、 from W Derived classes 、 Or pointer ::type by W Dereference type or from W Derived classes .
Specify when calling T Or when not using ptr、ref Or call the policy
NonCopyable Prohibit automatic registration and replication W Example of to_python transformation . When W When there is no publicly accessible copy constructor . If there is , Must be boost::noncopyable

4.2 Constructors

// 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 name Is essential beyond , Others can be defaulted . There is still a lot of room for users to play freely . It should be noted that , If in class_ There is no explicit identification when instantiating "no_init", Doesn't mean the class doesn't have a constructor or init(), It's just that constructors don't need arguments . So for classes without constructors ( Abstract class ), Need explicit identification "no_init" Of .

In fact, in addition to the list given on the official website , There are other constructors to call , concrete boost Source code .

4.2 Encapsulate member exchange function

class_ The member function of def() Function to encapsulate c++ Member function or non member function in class , 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 , In this way, the constructor of the class can be encapsulated independently .

name Express python Encapsulated function name ,F/Fn Is the corresponding c++ The name of the function ,A1/A2/A3 Represents the property , Can correspond to docstring/policies/keywords, These three can appear in any number and order . stay Boost.Python in , Including the parameters args(), return type return_value_policy() And so on /obj In the form of .

A1-A3 Corresponding to the contents of the following table :

name attribute explain
docstringAny ntbs The value will be bound to python Methodical __doc__ Properties of the
policiesCallPolicies Model Encapsulation strategy of function results
keywords Parameters Used to represent function parameters

4.3 Encapsulate member variables

The member variable here corresponds to c++ Member variables in class , Content that can be accessed directly from the outside , It can be encapsulated as read-only according to different properties / There are two types of readable and writable .

// 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: After encapsulation, it is in python The name of the variable in the class ;
  • d: Corresponding to c++ The variable of

4.4 Create properties

The attribute here corresponds to c++ Private or protected variables in a class , Such variables cannot be accessed directly from the outside , Only through the access interface within 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 variables 
self& add_static_property(char const* name, Get fget)
self& add_static_property(char const* name, Get fget, Set fset)

When adding private variables , Followed by reading and setting methods , Note that at least a read interface needs to be provided (Get).

Create a new Python Property class instance , Will have ( Optional ) docstring doc Of object(fget)( And the second form of object(fset)) Passed to its constructor , This property is then added to the being constructed with the given property name Python The class object .

4.5 Declare static functions

class_& staticmethod(char const* name);

take name The specified function is declared as python Static function under , amount to python sentence :

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


5. summary

Only part of it is shown here class_ Methods provided , It's enough for most scenes ( I guess. ), There are other interfaces , We will analyze it in detail when we need to use it later , There's no guessing here .

Reference material

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

copyright notice
author[Xiangdi],Please bring the original link to reprint, thank you.
https://en.pythonmana.com/2022/131/202205110608082257.html

Random recommended