描述: 本教程阐述一种在python中使用C++类的方法。
关键词: C++, Python, 绑定
教程级别: 高级
本教程演示了一种在 Python 中使用带有 ROS 消息的
C++ 类的方法。使用 Boost Python 库。难点在于将用纯 Python 编写的
ROS 消息的 Python 对象转换为等效的 C++ 实例。此转换将通过序列化/反序列化完成。源文件可在
https://github.com/galou/python_bindings_tutorial
中找到。
另一种解决方案是使用经典的ROS服务,用C++编写的服务器将是C++类的包装器,客户端C++或Python将调用该服务。这里提出的解决方案不会创建
ROS 节点,前提是要包装的类不使用 ros::NodeHandle。
另一种解决方案是为所有需要的 ROS 消息及其依赖项编写包装器。一些显然被弃用的软件包为这个任务的自动化提出了一些解决方案:genpybindings
和 boost_ python_ros。
1没有 NodeHandle 的类
因为 roscpp 在调用 rospy.init_node 时没有初始化。ros::NodeHandle
实例不能在 C++ 类中使用,而不会生成错误。如果 C++ 不使用 ros::NodeHandle,这不是问题。
1.1创建包并编写 C++ 类
创建一个包并创建 C++ 类,我们将要为其进行 Python 绑定。此类使用 ROS 消息作为参数和返回类型。
1 catkin_create_pkg python_bindings_tutorial rospy roscpp std_msgs
2 cd python_bindings_tutorial/include/python_bindings_tutorial
3 touch add_two_ints.h
4 rosed python_bindings_tutorial add_two_ints.h
|
include/python_bindings_tutorial/add_two_ints.h
的内容为:
1 #ifndef PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H
2 #define PYTHON_BINDINGS_TUTORIAL_ADD_TWO_INTS_H
3
4 #include <std_msgs/Int64.h>
5
6 namespace python_bindings_tutorial {
7
8 class AddTwoInts
9 {
10 public:
11 std_msgs::Int64 add(const std_msgs::Int64& a, const std_msgs::Int64& b);
12 };
13
14 }
15
16 #endif
17
|
将类实现写入 。
roscd python_bindings_tutorial/src
touch add_two_ints.cpp
rosed python_bindings_tutorial add_two_ints.cpp
|
src/add_two_ints.cpp的内容将是:
1 #include <python_bindings_tutorial/add_two_ints.h>
2
3 using namespace python_bindings_tutorial;
4
5 std_msgs::Int64 AddTwoInts::add(const std_msgs::Int64& a, const std_msgs::Int64& b)
6 {
7 std_msgs::Int64 sum;
8 sum.data = a.data + b.data;
9 return sum;
10 }
|
1.2绑定,C++ 部分
绑定通过两个包装类进行,一个在 C++ 中,一个在 Python 中。C++ 包装将序列化内容的输入转换为
C++ 消息实例,并将 C++ 消息实例的输出转换为序列化内容。
1 roscd python_bindings_tutorial/src
2 touch add_two_ints_wrapper.cpp
3 rosed python_bindings_tutorial add_two_ints_wrapper.cpp
|
src/add_two_ints_wrapper.cpp的内容将是:
1 #include <boost/python.hpp>
2
3 #include <string>
4
5 #include <ros/serialization.h>
6 #include <std_msgs/Int64.h>
7
8 #include <python_bindings_tutorial/add_two_ints.h>
9
10
11
12 template <typename M>
13 M from_python(const std::string str_msg)
14 {
15 size_t serial_size = str_msg.size();
16 boost::shared_array<uint8_t> buffer(new uint8_t[serial_size]);
17 for (size_t i = 0; i < serial_size; ++i)
18 {
19 buffer[i] = str_msg[i];
20 }
21 ros::serialization::IStream stream(buffer.get(), serial_size);
22 M msg;
23 ros::serialization::Serializer<M>::read(stream, msg);
24 return msg;
25 }
26
27
28
29 template <typename M>
30 std::string to_python(const M& msg)
31 {
32 size_t serial_size = ros::serialization::serializationLength(msg);
33 boost::shared_array<uint8_t> buffer(new uint8_t[serial_size]);
34 ros::serialization::OStream stream(buffer.get(), serial_size);
35 ros::serialization::serialize(stream, msg);
36 std::string str_msg;
37 str_msg.reserve(serial_size);
38 for (size_t i = 0; i < serial_size; ++i)
39 {
40 str_msg.push_back(buffer[i]);
41 }
42 return str_msg;
43 }
44
45 class AddTwoIntsWrapper : public python_bindings_tutorial::AddTwoInts
46 {
47 public:
48 AddTwoIntsWrapper() : AddTwoInts() {}
49
50 std::string add(const std::string& str_a, const std::string& str_b)
51 {
52 std_msgs::Int64 a = from_python<std_msgs::Int64>(str_a);
53 std_msgs::Int64 b = from_python<std_msgs::Int64>(str_b);
54 std_msgs::Int64 sum = AddTwoInts::add(a, b);
55
56 return to_python(sum);
57 }
58 };
59
60 BOOST_PYTHON_MODULE(_add_two_ints_wrapper_cpp)
61 {
62 boost::python::class_<AddTwoIntsWrapper>("AddTwoIntsWrapper", boost::python::init<>())
63 .def("add", &AddTwoIntsWrapper::add)
64 ;
65 }
|
No code_block found 行以动态库的形式创建 Python 模块。模块的名称必须是 CMakeLists.txt
中导出的库的名称。
1.3绑定,Python 部分
Python 包装器将 Python 消息实例的输入转换为序列化内容,并将序列化内容的输出转换为
Python 消息实例。从 Python 序列化内容 (str) 到 C++ 序列化内容 (std::string)
的转换是在 Boost Python 库中构建的。
roscd python_bindings_tutorial/src
mkdir python_bindings_tutorial
roscd python_bindings_tutorial/src/python_bindings_tutorial
touch _add_two_ints_wrapper_py.py
rosed python_bindings_tutorial _add_two_ints_wrapper_py.py
|
src/python_bindings_tutorial/_add_two_ints_wrapper_py.py 的内容将是
1 from StringIO import StringIO
2
3 import rospy
4 from std_msgs.msg import Int64
5
6 from python_bindings_tutorial._add_two_ints_wrapper_cpp import AddTwoIntsWrapper
7
8
9 class AddTwoInts(object):
10 def __init__(self):
11 self._add_two_ints = AddTwoIntsWrapper()
12
13 def _to_cpp(self, msg):
14 """Return a serialized string from a ROS message
15
16 Parameters
17 ----------
18 - msg: a ROS message instance.
19 """
20 buf = StringIO()
21 msg.serialize(buf)
22 return buf.getvalue()
23
24 def _from_cpp(self, str_msg, cls):
25 """Return a ROS message from a serialized string
26
27 Parameters
28 ----------
29 - str_msg: str, serialized message
30 - cls: ROS message class, e.g. sensor_msgs.msg.LaserScan.
31 """
32 msg = cls()
33 return msg.deserialize(str_msg)
34
35 def add(self, a, b):
36 """Add two std_mgs/Int64 messages
37
38 Return a std_msgs/Int64 instance.
39
40 Parameters
41 ----------
42 - a: a std_msgs/Int64 instance.
43 - b: a std_msgs/Int64 instance.
44 """
45 if not isinstance(a, Int64):
46 rospy.ROSException('Argument 1 is not a std_msgs/Int64')
47 if not isinstance(b, Int64):
48 rospy.ROSException('Argument 2 is not a std_msgs/Int64')
49 str_a = self._to_cpp(a)
50 str_b = self._to_cpp(b)
51 str_sum = self._add_two_ints.add(str_a, str_b)
52 return self._from_cpp(str_sum, Int64)
|
以便能够将类导入为python_bindings_tutorial。AddTwoInts,我们导入
__init__.py 中的符号。首先,我们创建文件:
1 roscd python_bindings_tutorial/src/python_bindings_tutorial
2 touch __init__.py
3 rosed python_bindings_tutorial __init__.py
|
src/python_bindings_tutorial/__init__.py的内容为:
1 from python_bindings_tutorial._add_two_ints_wrapper_py import AddTwoInts
|
1.4将所有东西粘合在一起
编辑 CMakeLists.txt (rosed python_bindings_tutorial
CmakeLists.txt) 如下所示:
cmake_minimum_required(VERSION 2.8.3)
project(python_bindings_tutorial)
find_package(catkin REQUIRED COMPONENTS
roscpp
roscpp_serialization
std_msgs
)
## Both Boost.python and Python libs are required.
find_package(Boost REQUIRED COMPONENTS python)
find_package(PythonLibs 2.7 REQUIRED)
## Uncomment this if the package has a setup.py. This macro ensures
## modules and global scripts declared therein get installed
## See http://ros.org/doc/api/catkin/html/user_guide/setup_dot_py.html
catkin_python_setup()
###################################
## catkin specific configuration ##
###################################
catkin_package(
INCLUDE_DIRS include
LIBRARIES add_two_ints _add_two_ints_wrapper_cpp
CATKIN_DEPENDS roscpp
# DEPENDS system_lib
)
###########
## Build ##
###########
# include Boost and Python.
include_directories(
include
${catkin_INCLUDE_DIRS}
${Boost_INCLUDE_DIRS}
${PYTHON_INCLUDE_DIRS}
)
## Declare a cpp library
add_library(add_two_ints src/add_two_ints.cpp)
add_library(_add_two_ints_wrapper_cpp src/add_two_ints_wrapper.cpp)
## Specify libraries to link a library or executable target against
target_link_libraries(add_two_ints ${catkin_LIBRARIES})
target_link_libraries(_add_two_ints_wrapper_cpp add_two_ints ${catkin_LIBRARIES} ${Boost_LIBRARIES})
# Don't prepend wrapper library name with lib and add to Python libs.
set_target_properties(_add_two_ints_wrapper_cpp PROPERTIES
PREFIX ""
LIBRARY_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_PACKAGE_PYTHON_DESTINATION}
)
|
c++ 包装库应与 Python 模块同名。如果由于某种原因需要不同目标名称,则可以使用 set_target_properties(_add_two_ints_wrapper_cpp
PROPERTIES OUTPUT_NAME correct_library_name) 指定库名称。
生产线
用于导出 Python 模块,并与文件相关联 setup.py
1 roscd python_bindings_tutorial
2 touch setup.py
|
setup.py 的内容将是:
1
2
3 from distutils.core import setup
4 from catkin_pkg.python_setup import generate_distutils_setup
5
6
7 setup_args = generate_distutils_setup(
8 packages=['python_bindings_tutorial'],
9 package_dir={'': 'src'})
10
11 setup(**setup_args)
|
然后,我们使用 catkin_make 构建包。
1.5测试绑定
现在,您可以在 Python 脚本或 Python shell 中使用绑定
1 from std_msgs.msg import Int64
2 from python_bindings_tutorial import AddTwoInts
3 a = Int64(4)
4 b = Int64(2)
5 addtwoints = AddTwoInts()
6 sum = addtwoints.add(a, b)
7 sum
|
2具有 NodeHandle 的类
如前所述,对 rospy.init_node 的 Python 调用不会初始化 roscpp。如果未初始化
roscpp,则实例化 ros::NodeHandle 将导致致命错误。moveit_ros_planning_interface为此提供了解决方案。在任何使用包装类的
Python 脚本中,在实例化 AddTwoInts 之前需要添加两行:
1 from moveit_ros_planning_interface._moveit_roscpp_initializer import roscpp_init
2 roscpp_init('node_name', [])
|
这将创建一个 ROS 节点。因此,与经典的 ROS 服务服务器/客户端实现相比,这种方法的优势并不像不需要
ros::NodeHandle 的情况那样明显。
3带有 ROS 消息容器的类
如果类使用 ROS 消息的容器,则必须添加额外的转换步骤。此步骤并非特定于 ROS,而是 Boost
Python 库的一部分。
3.1“std::vector<M>”作为返回类型
在定义 C++ 包装类的文件中,添加以下行:
1
2 template<class T>
3 struct vector_to_python
4 {
5 static PyObject* convert(const std::vector<T>& vec)
6 {
7 boost::python::list* l = new boost::python::list();
8 for(std::size_t i = 0; i < vec.size(); i++)
9 (*l).append(vec[i]);
10
11 return l->ptr();
12 }
13 };
14
15 class Wrapper : public WrappedClass
16 {
17
18
19
20 std::vector<std::string> wrapper_fun(const std::string str_msg)
21 {
22
23 }
24
25 };
26
27 BOOST_PYTHON_MODULE(module_wrapper_cpp)
28 {
29 boost::python::class_<Wrapper>("Wrapper", bp::init<>())
30 .def("fun", &Wrapper::wrapper_fun);
31
32 boost::python::to_python_converter<std::vector<std::string, std::allocator<std::string> >, vector_to_python<std::string> >();
33 }
|
3.2 'std::vector<M>' 作为参数类型
参见 Boost.Python 文档。 |