C++python 混合编程 -boost

学习转自:JasonLiThirty

一、前言

1.1 boost

boost::python 用于将 C++ 的函数和对象导出, 方便 python 调用 对象和方法,用来实现 C++ 和 Python 的混合编程。

一言概之:boost 库的作用就是:将 C++ 的对象 转化 为 python 能直接调用的对象。用 C++ 写程序,用 python 解释器调用。

1.2 安装

库的安装参阅:C++ 和 Python 的混合编程 -Boost::python 的编译和配置 - 简书 (jianshu.com)

1.3 前菜

直接看一个 C++ 和 python 混合编程的例子:

#define BOOST_PYTHON_STATIC_LIB   //boost::python 库的 config.hpp 中规定,如没定义 BOOST_PYTHON_STATIC_LIB ,则采用动态编译的库

#include <boost/python.hpp>
#include <iostream>

struct StructionData
{
    void hello()
    {
        std::cout << "hello, this is boost::python sample!" << std::endl;
    }
    void printmsg()
    {
        std::cout << "print message done!" << std::endl;
    }
};

BOOST_PYTHON_MODULE(Boost_Python_Sample)
{
    //struct
    boost::python::class_<StructionData>("StructionData")
        .def("hello_p", &StructionData::hello)
        .def("printmsg_p", &StructionData::printmsg);
}

上面第 6-16 行定义的一个 C++ 结构体,第 18-24 行写了一个 BOOST_PYTHON_MODULE,用来导出 C++ 的结构体,以供 python 使用。

  • BOOST_PYTHON_MODULE里面传的为自定义的模块名。python 调用的 C++ 结构体的时候,需要导入该库名。
  • boost::python::class_<StructionData>("StructionData"),前面部分说明导出的是结构体,后面括号里 StructionData 是自定义的对象名。
  • def 中第二个参数 StructionData::hello 为 C++ 结构体的函数名,第一个参数 hello 为导出的函数名,供 python 调用的。

下面看 python 解释器运行上面的代码:

>>> import Boost_Python_Sample
>>> Ptr = Boost_Python_Sample.StructionData
>>> Ptr.hello_p()
hello, this is boost::python sample!
>>> Ptr.printmsg_p()
print message done!

二、C++ 数据类型的导出和调用

2.1 函数 Function

格式:boost::python::def(" 导出的 python 函数名 ", c++ 函数名)

看一个例子

void greet()
{
    cout<< "Hello world!" <<endl;
}

BOOST_PYTHON_MODULE(Python_Wrapper)
{
    boost::python::def("greet", greet);
}

//python 解释器运行
>>> import Python_Wrapper
>>> Python_Wrapper.greet()
'Hello world!'

2.2 类 class

2.2.1 无参构造

格式:

boost::python::class_<T>("TName")
     .def("func1", &T::func1)
     .def("func2", &T::func2); 
  • T 是需要被导出的 C++ 类名
  • TName 是导出的自定义的类名

看一个例子:

class Person {
public:
    void set(string name) {
        m_name = name;
    }
    string get() {
        return m_name;
    }
private:
    string m_name;
};

BOOST_PYTHON_MODULE(Python_Wrapper)
{
    boost::python::class_<Person>("Person")
     .def("set", &Person::set)
     .def("get", &Person::get); 
}

//python 解释器运行
>>> import Python_Wrapper
>>> msg = Python_Wrapper.Message()
>>> msg.Get()
''
>>> msg.Set('123')
>>> msg.Get()
'123'

2.2.2 有参构造

格式:boost::python::class_<T>("TName", boost::python::init<para>())

class Sum
{
public:
    Sum(std::string data) :m_data(data) {}
    Sum(double a, double b) :m_a(a), m_b(b) {}

    void Set(std::string data)
    {
        m_data = data;
    }

    std::string Get()
    {
        return m_data;
    }
    double Result()
    {
        return m_a + m_b;
    }
private:
    std::string m_data;
    double m_a;
    double m_b;
};

BOOST_PYTHON_MODULE(Python_Wrapper)
{
    boost::python::class_<Sum>("Sum", boost::python::init<std::string>())
        .def(boost::python::init<double, double>())
        .def("Set", &Sum::Set)
        .def("Get", &Sum::Get)
        .def("Result", &Sum::Result);
}


//python 解释器运行
>>> import Python_Wrapper
>>> s1 = Python_Wrapper.Sum("total")
>>> s1.Get()
'total'
>>> s2 = Python_Wrapper.Sum(1,2)
>>> s2.Result()
3.0

2.2.3 成员属性

格式:.def_readonly()/.def_readwrite()

class User
{
public:
    User(std::string name) :m_name(name), m_number(-1) {}
    std::string m_name;
    int m_number;
};
BOOST_PYTHON_MODULE(Python_Wrapper)
{
    boost::python::class_<User>("User", boost::python::init<std::string>())
        .def_readonly("name", &User::m_name)
        .def_readwrite("number", &User::m_number);
}
//python 解释器运行
>>> import Python_Wrapper
>>> user = Python_Wrapper.User("Jason")
>>> user.name = "Micky"
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute
>>> user.number = 12345
>>> user.number
12345

2.2.4 增加类属性

格式:.add_property()

class MessagePro
{
public:
    void Set(std::string msg)
    {
        m_msg = msg;
    }
    std::string Get()
    {
        return m_msg;
    }
private:
    std::string m_msg;
};

BOOST_PYTHON_MODULE(boost_python)
{
    boost::python::class_<MessagePro>("MessagePro")
        .add_property("info", &MessagePro::Get, &MessagePro::Set);
}

//python 解释器运行
>>> import Python_Wrapper
>>> msg = Python_Wrapper.MessagePro()
>>> msg.set()  // 报错
>>> msg.get()  // 报错
>>> msg.info
''
>>> msg.info = 'hello'
>>> msg.info
'hello'

2.2.5 继承

格式:boost::python::class_<T, boost::python::bases<TBase>>("TName")

必须告知导出原 C++ 类的继承关系,不然导出后类之间就没有了继承关系

告知类的继承关系关系后:

  • 继承类自动继承了基类的 Python 方法(即包装了的 c++ 成员函数)
  • 即使是基类指针指向继承类对象,多态的函数也能够找到相应继承类的对应函数
class Base
{
public:
    virtual ~Base() {};
    virtual std::string Name()
    {
        return "Base";
    }
};

class Derived : public Base
{
public:
    std::string Name()
    {
        return "Derived";
    }
};

void BaseName(Base *base)
{
    std::cout << base->Name().c_str() << std::endl;
}

void DerivedName(Derived *derived)
{
    std::cout << derived->Name().c_str() << std::endl;
}

Base *factory()
{
    return new Derived();
}
BOOST_PYTHON_MODULE(boost_python)
{
    //inherited
    boost::python::class_<Base>("Base", boost::python::init<>())
        .def("Name", &Base::Name);

    boost::python::class_<Derived, boost::python::bases<Base>>("Derived")
        .def("Name", &Derived::Name);
    boost::python::def("BaseName", BaseName);
    boost::python::def("DerivedName", DerivedName);

    // 因为 factory 是生成一个新的 Direved 对象
    //manage_new_object 告知 Python 生成一个指针指向一个新生成的 Python 对象,
    boost::python::def("factory", factory, boost::python::return_value_policy<boost::python::manage_new_object>());
}
//python
>>> import Python_Wrapper
>>> obj = Python_Wrapper.factory()
>>> obj.Name()
'Derived'
>>> Python_Wrapper.BaseName(obj)
Derived
>>> Python_Wrapper.DerivedName(obj)
Derived
>>>
>>> objBase = Python_Wrapper.Base()
>>> objBase.Name()
'Base'
>>> Python_Wrapper.BaseName(objBase)
Base
>>> Python_Wrapper.DerivedName(objBase)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
    Python_Wrapper.DerivedName(Base)
did not match C++ signature:

2.2.6 其他

虚函数,操作符,重载等类的一些属性,参阅C++ 和 Python 的混合编程 -C++ 数据类型的导出和调用 - 简书 (jianshu.com)

2.3 枚举 Enum

格式:boost::python::enum_<T>("TName")

enum MessageType
{
    MT_START = 1,
    MT_PROCESS,
    MT_DONE,
    MT_EXCEPTION
};
BOOST_PYTHON_MODULE(Python_Wrapper)
{
//enum
    boost::python::enum_<MessageType>("MessageType")
        .value("MT_START", MT_START)
        .value("MT_PROCESS", MT_PROCESS)
        .value("MT_DONE", MT_DONE)
        .value("MT_EXCEPTION", MT_EXCEPTION);
}
//python
>>> import Python_Wrapper
>>> Python_Wrapper.MessageType.MT_START
Python_Wrapper.MessageType.MT_START
>>> int(Python_Wrapper.MessageType.MT_START)
1
>>> int(Python_Wrapper.MessageType.MT_DONE)
3

2.2 常量 const

2.2.1 基本常量

格式:boost::python::scope().attr

//1. 在模块中加入常量属性
BOOST_PYTHON_MODULE(Python_Wrapper)
{
    //const
    boost::python::scope().attr("yes") = 1;
    boost::python::scope().attr("no") = 0;
}
//python
>>> import Python_Wrapper
>>> Python_Wrapper.yes
1
>>> Python_Wrapper.no
0
  • boost::python::scope()用于得到 当前的作用域,定义新的 scope 对象会改变当前的作用域

  • 使用参数来构造一个新的 scope 对象会将关联的全局 python 对象更改为参数所持有的对象 直到作用域对象的生存期结束, 关联的全局 python 对象才会恢复到作用域对象之前的对象。

2.2.2 作用域

class Message
{
public:
    void Set(std::string msg)
    {
        m_msg = msg;
    }
    std::string Get()
    {
        return m_msg;
    }
private:
    std::string m_msg;
};

//1. 在模块中加入常量属性
BOOST_PYTHON_MODULE(Python_Wrapper)
{
    //const
    boost::python::scope().attr("yes") = 1;
    boost::python::scope().attr("no") = 0;

    boost::python::class_<Message>("Message")
        .def("Set", &Message::Set)
        .def("Get", &Message::Get); 
}

//python
>>> import Python_Wrapper
>>> Python_Wrapper.yes
1
>>> Python_Wrapper.no
0
///////////////////////////////////////////////////////
//2. 改变导出顺序,也没有问题,在模块中加入常量属性
BOOST_PYTHON_MODULE(boost_python)
{
    boost::python::class_<Message>("Message")
        .def("Set", &Message::Set)
        .def("Get", &Message::Get); 

    //const
    boost::python::scope().attr("yes") = 1;
    boost::python::scope().attr("no") = 0;
}

//python
>>> import Python_Wrapper
>>> Python_Wrapper.yes
1
>>> Python_Wrapper.no
0
//////////////////////////////////////////////////////
//3. 如果使用 boost::python::scope 对象,则改变了当前的作用域,yes 和 no 成了 message 类的属性
BOOST_PYTHON_MODULE(boost_python)
{
    //Change the current scope
    boost::python::scope newScope =  boost::python::class_<Message>("Message")
        .def("Set", &Message::Set)
        .def("Get", &Message::Get);

    //const Defined in the current scope(Message)
    boost::python::scope().attr("yes") = 1;
    boost::python::scope().attr("no") = 0;
}

//python
>>> import Python_Wrapper
>>> Python_Wrapper.yes
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'Python_Wrapper' has no attribute 'yes'
>>> Python_Wrapper.no
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: module 'Python_Wrapper' has no attribute 'no'
>>> msg = Python_Wrapper.Message()
>>> msg.yes
1
>>> msg.no
0
//4. 使用 boost::python::scope 定义了新的域对象,改变了当前的作用域,这个对象出了作用域,则会恢复为之前的域对象
BOOST_PYTHON_MODULE(Python_Wrapper)
{
    //const
    {
    boost::python::scope newScope =  boost::python::class_<Message>("Message")
        .def("Set", &Message::Set)
        .def("Get", &Message::Get);

    boost::python::scope().attr("yes") = 1;
    boost::python::scope().attr("no") = 0;
    }

    boost::python::scope().attr("exist") = 1;
    boost::python::scope().attr("none") = 0;
}
//python
>>> import Python_Wrapper
>>> msg = Python_Wrapper.Message()
>>> msg.yes
1
>>> msg.no
0
>>> Python_Wrapper.exist
1
>>> Python_Wrapper.none
0

上面介绍了如何将 C++ 代码导出为 python 解释器能够调用的对象。因此我们可以利用 Boost::Python 从 Python 调用 C ++ 代码。

三、C++ 执行 python

这一部分内容介绍:C++ 调用执行 python 代码。

bp::str code = "def fact(n):\n\treturn 1 if n==1 else n*fact(n-1)")
bp::object result = bp::exec(code)  

bp::exec 的作用就是执行 python 代码。

3.1 exec 函数

函数作用:用来执行一个字符串形式 python 语句,或者表达式,并返回得到的计算结果。

函数原型:

boost::pythoin::api::object exec(boost::python::str string,
                                 boost::python::api::object global=boost::python::api::object(),
                                 boost::python::api::object local=boost::python::api::object());

参数:

  • string:需要执行的语句或表达式字符串,如 result=2**10 #2^10
  • global:需要执行的字符串是放在哪个全局作用域中。
  • local:需要执行的字符串是放在哪个局部作用域中。
  • 返回的值是 Boost::Python::api 中的 Object 对象。
  • 可以使用 extract 来获得 C++ 类型的值。

举个例子:

//C++
boost::python::object main_module = import("__main__");
boost::python::object main_namespace = main_module.attr("__dict__");
boost::python::object return_value = exec("result = 2**10", global = main_namespace,local = main_namespace);

3.2 eval 函数

函数作用:用来执行一个 python 求值的表达式的字符串, 并返回得到的计算结果。

原型:

namespace bpa = boost::python::api;
bpa::object bpa::eval(boost::python::str string, 
                      bpa::object global=bpa::object(), bpa::object local = bpa::object())

参数:

  • string:需要执行的表达式字符串,如 2**10
  • global:需要执行的字符串是放在哪个全局作用域中。
  • local:需要执行的字符串是放在哪个局部作用域中。
  • 返回的值是 Boost::Python::api中的 Object 对象。

举个例子:

//C++
boost::python::object main_module = import("__main__");
boost::python::object main_namespace = main_module.attr("__dict__");
boost::python::object return_value = eval("2**10", main_namespace,main_namespace);

3.3 eval 与 exec 区别

eval 与 exec 的调用形式相同,作用也基本相同,区别是:

  • eval :evaluate Python expression from str

    用来直接运行一个表达式求得一个值,而不是运行一个语句,eval 需要有返回的值。

  • exec:execute python expression/statement from str

    用来运行一个表达式或者运行一个语句,可以有返回值,也可以没有。

3.4 exec_file 函数

函数作用:执行 python 源文件 Execute python source code from file ‘filename’.

原型:

namespace bpa=boost::python::api;
bpa::object exec_file(boost::python::str filename, 
                      bpa::object global=bpa::object(), bpa::object local=pba::object())

参数:

  • filename:需要执行的路径和文件名。
  • global:需要执行的字符串是放在哪个全局作用域中。
  • local:需要执行的字符串是放在哪个局部作用域中。

举个例子:

//C++
boost::python::object main_module = import("__main__");
boost::python::object main_namespace = main_module.attr("__dict__");
boost::python::object simple = exec_file("D:\\demoPython\\example.py", main_namespace,main_namespace);

欢迎各位看官及技术大佬前来交流指导呀,可以邮件至 jqiange@yeah.net