简体   繁体   English

downcast到pybind11派生类

[英]Downcast to pybind11 derived class

I'm using the " Overriding virtual functions in Python " feature of pybind11 to create Python classes that inherit from C++ abstract classes. 我正在使用pybind11的“ 在Python中覆盖虚函数 ”功能来创建从C ++抽象类继承的Python类。 I have a C++ class State which is subclassed in Python as MyState . 我有一个C ++类State ,它在Python中MyState类化为MyState In this situation I have some MyState object that lost its type information and Python thinks it's a State . 在这种情况下,我有一些MyState对象丢失了它的类型信息,Python认为它是一个State I need to downcast it back to MyState in Python code and I don't know a good way to do this. 我需要在Python代码MyStateMyStateMyState ,我不知道这样做的好方法。

Here's the C++ example code: 这是C ++示例代码:

#include <memory>

#include <pybind11/pybind11.h>

namespace py = pybind11;

// ========== State ==========

class State {
 public:
  virtual ~State() = default;

  virtual void dump() = 0;
};

using StatePtr = std::shared_ptr<State>;

class PyState : public State {
 public:
  using State::State;

  void dump() override {
    PYBIND11_OVERLOAD_PURE(void, State, dump);
  }
};

// ========== Machine ==========

class Machine {
 public:
  virtual ~Machine() = default;

  virtual StatePtr begin() = 0;

  virtual StatePtr step(const StatePtr&) = 0;
};

using MachinePtr = std::shared_ptr<Machine>;

class PyMachine : public Machine {
 public:
  using Machine::Machine;

  StatePtr begin() override {
    PYBIND11_OVERLOAD_PURE(StatePtr, Machine, begin);
  }

  StatePtr step(const StatePtr& state) override {
    PYBIND11_OVERLOAD_PURE(StatePtr, Machine, step, state);
  }
};

// ========== run ==========

void run(const MachinePtr& machine) {
  StatePtr state = machine->begin();
  for (int i = 0; i < 5; ++i) {
    state = machine->step(state);
    state->dump();
  }
}

// ========== pybind11 ==========

PYBIND11_MODULE(example, m) {
  py::class_<State, StatePtr, PyState>(m, "State").def(py::init<>());
  py::class_<Machine, MachinePtr, PyMachine>(m, "Machine")
      .def(py::init<>())
      .def("begin", &Machine::begin)
      .def("step", &Machine::step);
  m.def("run", &run, "Run the machine");
}

And the Python code: 和Python代码:

#!/usr/bin/env python3

from example import Machine, State, run


class MyState(State):
    def __init__(self, x):
        State.__init__(self)
        self.x = x

    def dump(self):
        print(self.x)


class MyMachine(Machine):
    def __init__(self):
        Machine.__init__(self)

    def begin(self):
        return MyState(0)

    def step(self, state):
        # problem: when called from C++, `state` is an `example.State`
        # instead of `MyState`. In order to access `state.x` we need
        # some way to downcast it...
        return MyState(state.x + 1)


machine = MyMachine()

print("running machine with python")
state = machine.begin()
for _ in range(5):
    state = machine.step(state)
    state.dump()

print("running machine with C++")
run(machine)  # error

Error message: 错误信息:

running machine with python
1
2
3
4
5
running machine with C++
Traceback (most recent call last):
  File "<string>", line 38, in <module>
  File "<string>", line 36, in __run
  File "/usr/local/fbcode/platform007/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/local/fbcode/platform007/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/data/users/jcai/fbsource/fbcode/buck-out/dev/gen/experimental/jcai/pybind/run_example#link-tree/run_example.py", line 38, in <module>
    run(machine)  # error
  File "/data/users/jcai/fbsource/fbcode/buck-out/dev/gen/experimental/jcai/pybind/run_example#link-tree/run_example.py", line 26, in step
    return MyState(state.x + 1)
AttributeError: 'example.State' object has no attribute 'x'

I do have a hacky workaround where I basically keep a "downcast map" std::unordered_map<State*, py::object> and register every created MyState with it. 我确实有一个hacky解决方法,我基本上保持一个“downcast map” std::unordered_map<State*, py::object>并用它注册每个创建的MyState But I prefer not to resort to such things. 但我宁愿不诉诸这样的事情。

I think that you're probably suffering from this suite of problems: 我想你可能会遇到这套问题:

https://github.com/pybind/pybind11/issues/1774 https://github.com/pybind/pybind11/issues/1774

Ultimately, because you're just returning MyState straight out the gate, which then goes straight to C++, the Python interpreter loses track of your instance, and goes ahead and garbage collects the Python-portion of the object, which is why your object ends up getting kinda sliced . 最终,因为你只是直接返回MyState ,然后直接转到C ++,Python解释器会丢失你的实例跟踪,然后继续垃圾收集对象的Python部分,这就是你的对象结束的原因变得有点切片

Potential solutions: 潜在解决方案

  • Stash a reference to your return MyState ,at least long enough for the Python interpreter to get a reference again. 存储对返回MyState的引用,至少足以使Python解释器再次获得引用。
    • eg change return MyState(...) to self._stashed_state = MyState(...); return self._stashed_state 例如,将return MyState(...)更改为self._stashed_state = MyState(...); return self._stashed_state self._stashed_state = MyState(...); return self._stashed_state
  • See if you can somehow incref on the Python version of your class in C++ (yuck, but it'll work) 看看你是否可以用C ++以某种方式incref你的类的Python版本(哎呀,但它会起作用)
  • Review the workarounds listed in the aforementioned issues (can't remember all of 'em) 查看上述问题中列出的变通方法(不记得所有的'em)
  • Use our fork of pybind11 , which handles this, but also drags in other stuff: overview for RobotLocomotion/pybind11 使用pybind11的fork,它处理这个,但也拖入其他东西: RobotLocomotion概述/ pybind11

You may also want to post on one of the existing issues mentioning that you encountered this problem as well (just so that it can be tracked). 您可能还想发布一个现有问题,提到您遇到此问题(只是为了可以跟踪它)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM