Pybind11绑定类方法返回新实例而非原地修改问题
解决pybind11中类方法返回原输入实例的问题
问题背景
在pybind11绑定C++代码时,ObstacleProcess类的__call__方法处理Obstacle实例列表后,返回的是新创建的Obstacle实例而非原输入实例,导致Python中对象地址发生变化,不符合预期。
Python复现代码
import example obstacle_1 = example.Obstacle() obstacle_2 = example.Obstacle() obstacles = [obstacle_1, obstacle_2] print(obstacles) params = example.Params() obstacle_process = example.ObstacleProcess(params) obstacles = obstacle_process(obstacles) print(obstacles)
输出现象
第一次打印输出:
[<example.Obstacle object at 0x7fb65271e1b0>, <example.Obstacle at 0x7fb652735070>]
第二次打印输出:
[<example.Obstacle at 0x7fb652734670>, <example.Obstacle object at 0x7fb652735230>]
原obstacle_1的地址从0x7fb65271e1b0变为0x7fb652734670,说明返回的是新实例而非原输入实例。
相关C++代码
pybind11绑定代码
// pybind11 binding py::class_<Obstacle, std::shared_ptr<Obstacle>>(m, "Obstacle") .def(py::init<>()); py::class_<ObstacleProcess>(m, "ObstacleProcess") .def(py::init<const Params&>()) .def("__call__", &ObstacleProcess::operator_py<Params>, py::return_value_policy::reference);
operator_py实现
template <Params> std::vector<Obstacle>& operator_py( std::vector<Obstacle>& obstacles, const Params ¶meters ) { // 处理逻辑 ... return obstacles; }
已尝试方案
- 尝试使用/不使用
std::shared_ptr<Obstacle>,结果一致,说明shared_ptr绑定方式存在问题 - 尝试
PYBIND11_MAKE_OPAQUE(std::shared_ptr<std::vector<Obstacle>>);,无效果 - 尚未尝试pybind11的
smart_holder分支
解决方案
核心问题分析
当前代码中std::vector<Obstacle>存储的是Obstacle对象的值拷贝,而非指针或智能指针。当Python传入列表时,pybind11会将Python列表转换为std::vector<Obstacle>,这个过程会创建新的Obstacle实例(值拷贝),而非引用原实例。后续处理返回的是拷贝后的vector,因此Python中看到的是新对象。
修正步骤
- 修改C++数据结构
将std::vector<Obstacle>改为std::vector<std::shared_ptr<Obstacle>>,确保存储对象引用而非值:
template <Params> std::vector<std::shared_ptr<Obstacle>>& operator_py( std::vector<std::shared_ptr<Obstacle>>& obstacles, const Params ¶meters ) { // 处理逻辑:直接操作shared_ptr指向的原对象 ... return obstacles; }
- 调整pybind11绑定
- 为
std::vector<std::shared_ptr<Obstacle>>添加绑定:py::bind_vector<std::vector<std::shared_ptr<Obstacle>>>(m, "ObstacleList"); - 修改
__call__绑定,确保返回值策略正确并维持对象生命周期:.def("__call__", &ObstacleProcess::operator_py<Params>, py::return_value_policy::reference, py::keep_alive<1, 2>()); // 保证输入的vector在方法调用期间存活
- Python端适配
无需修改Python代码,pybind11会自动将Python的Obstacle实例列表转换为std::vector<std::shared_ptr<Obstacle>>。
关于smart_holder分支
不需要使用该分支,当前pybind11主分支已支持std::shared_ptr的正确引用传递,问题核心在于数据结构设计,而非绑定方式局限。
内容的提问来源于stack exchange,提问作者JMy
相关产品推荐
相关产品推荐

