静态方法(或工厂(?))创建的 boost python 对象中的虚拟覆盖

问题描述 投票:0回答:3

我正在尝试在 python 中创建一个类来覆盖 C++ 类中的(纯)虚函数(使用

boost.python
)。问题是 C++ 类是通过静态成员函数创建的(所有构造函数都是私有的或已删除)。我已经成功创建了 Base 类和 Python“知道”的 BaseWrap 类。我还能够创建一个可以在 python 中覆盖的纯虚函数。但是,我的问题是当 Base 的成员函数调用纯虚函数时。发生这种情况时,类找不到 python 实现,程序崩溃。

这里是C++代码:

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

#define CREATE(NAME) \
  static std::shared_ptr<NAME> Create() { \
    std::cout << "STATIC BASE CREATE" << std::endl; \
    return std::make_shared<NAME>();  \
  }

class Base {
protected:
  Base() { std::cout << "BASE DEFAULT CONSTRUCTOR" << std::endl; }
private:

  std::string CallSay() {
    return Say(); 
  }

  virtual std::string Say() const = 0;
};

class BaseWrap : public Base, public boost::python::wrapper<Base> {
public:
  BaseWrap() : Base() { std::cout << "BASEWRAP DEFAULT CONSTRUCTOR" << std::endl; }

  virtual std::string Say() const override
  {
    std::cout << "C++ Say" << std::endl;
    return this->get_override("say") ();
  }

  CREATE(BaseWrap)
};

BOOST_PYTHON_MODULE(Example)
{
  namespace python = boost::python;

  // Expose Base.
  python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
    .def("__init__", python::make_constructor(&BaseWrap::Create))
    .def("Say", python::pure_virtual(&Base::Say))
    .def("CallSay", &Base::CallSay);
}

和测试问题的python代码:

import sys
import Example

class PythonDerived(Example.Base):
    def __init__(self):
        print "PYTHON DEFAULT CONSTRUCTOR"
        Example.Base.__init__(self)

    def Say(self):
         return "Python Say"

d = PythonDerived()
print d
print 
print d.Say()
print
print d.CallSay()

运行时给出输出:

PYTHON DEFAULT CONSTRUCTOR
STATIC BASE CREATE
BASE DEFAULT CONSTRUCTOR
BASEWRAP DEFAULT CONSTRUCTOR
<__main__.PythonDerived object at 0x1091caf70>

Python Say

C++ Say
Traceback (most recent call last):
  File "test.py", line 20, in <module>
    print d.CallSay()
 TypeError: 'NoneType' object is not callable

看起来

Base::CallSay
方法正在寻找
BaseWrap::Say
的实现但找不到python实现。有谁知道为什么或如何使这项工作?

谢谢!

c++ python boost factory boost-python
3个回答
2
投票

这看起来好像是 Boost.Python 中的一个错误。

boost::python::wrapper
层次结构未在从
boost::python::make_constructor
返回的仿函数中初始化。由于
wrapper
层次结构没有 Python 对象的句柄,
get_override()
返回
NoneType
,并尝试调用
NoneType
引发
TypeError
异常。

为了解决这个问题,可以显式初始化

wrapper
层次结构。下面是一个完整的示例,它提供了实现此目的的通用方法。除了使用
make_constructor()
,还可以使用
make_wrapper_constructor()
。我选择不使用 C++11 功能。因此,将会有一些样板代码可以使用可变参数模板来减少,但是移植到 C++11 应该是相当简单的。

#include <iostream>
#include <boost/function_types/components.hpp>
#include <boost/function_types/result_type.hpp>
#include <boost/make_shared.hpp>
#include <boost/mpl/insert.hpp>
#include <boost/python.hpp>

namespace detail {

/// @brief wrapper_constructor will force the initialization
///        of the wrapper hierarchy when a class is held by
///        another type and inherits from boost::python::wrapper.
template <typename Fn>
class wrapper_constructor
{
public:

  typedef typename boost::function_types::result_type<Fn>::type result_type;

public:

  /// @brief Constructor.
  wrapper_constructor(Fn fn)
    : constructor_(boost::python::make_constructor(fn))
  {}

  /// @brief Construct and initialize python object.
  result_type operator()(boost::python::object self)
  {
    constructor_(self);
    return initialize(self);
  }

  /// @brief Construct and initialize python object.
  template <typename A1>
  result_type operator()(boost::python::object self, A1 a1)
  {
    constructor_(self, a1);
    return initialize(self);
  }

  // ... overloads for arguments, or use variadic templates.

private:

  /// @brief Explicitly initialize the wrapper.
  static result_type initialize(boost::python::object self)
  {
    // Extract holder from self.
    result_type ptr = boost::python::extract<result_type>(self);

    // Explicitly initialize the boost::python::wrapper hierarchy.
    initialize_wrapper(self.ptr(),        // PyObject.
                       get_pointer(ptr)); // wrapper hierarchy.

    return ptr;
  }

private:
  boost::python::object constructor_;
};

} // namespace detail

/// @brief Makes a wrapper constructor (constructor that works with
///        classes inheriting from boost::python::wrapper).
template <typename Fn>
boost::python::object make_wrapper_constructor(Fn fn)
{
  // Python constructors take the instance/self argument as the first
  // argument.  Thus, inject the 'self' argument into the provided
  // constructor function type.
  typedef typename boost::function_types::components<Fn>::type
      components_type;
  typedef typename boost::mpl::begin<components_type>::type begin;
  typedef typename boost::mpl::next<begin>::type self_pos;
  typedef typename boost::mpl::insert<
    components_type, self_pos, boost::python::object>::type signature_type;

  // Create a callable python object that defers to the wrapper_constructor.
  return boost::python::make_function(
    detail::wrapper_constructor<Fn>(fn),
    boost::python::default_call_policies(),
    signature_type());
}

class Base
{
protected:
  Base(int x) : x(x) { std::cout << "BASE DEFAULT CONSTRUCTOR" << std::endl; }
  virtual ~Base() {}
  int x;
public:
  std::string CallSay() { return Say(); }
  virtual std::string Say() const = 0;
};

class BaseWrap:
  public Base,
  public boost::python::wrapper<Base>
{
public:
  BaseWrap(int x):
    Base(x)
  { std::cout << "BASEWRAP DEFAULT CONSTRUCTOR" << std::endl; }

  virtual std::string Say() const 
  {
    std::cout << "C++ Say: " << x << std::endl;
    return this->get_override("Say")();
  }

  static boost::shared_ptr<BaseWrap> Create(int x)
  {
    return boost::make_shared<BaseWrap>(x);
  }
};

BOOST_PYTHON_MODULE(example)
{
  namespace python = boost::python;

  // Expose Base.
  python::class_<BaseWrap, boost::shared_ptr<BaseWrap>,
                 boost::noncopyable>("Base", python::no_init)
    .def("__init__", make_wrapper_constructor(&BaseWrap::Create))
    .def("Say", python::pure_virtual(&Base::Say))
    .def("CallSay", &Base::CallSay)
    ;
}

及其用法:

>>> import example
>>> class PythonDerived(example.Base):
...     def __init__(self, x):
...         print "PYTHON DEFAULT CONSTRUCTOR"
...         example.Base.__init__(self, x)
...     def Say(self):
...          return "Python Say"
... 
>>> d = PythonDerived(5)
PYTHON DEFAULT CONSTRUCTOR
BASE DEFAULT CONSTRUCTOR
BASEWRAP DEFAULT CONSTRUCTOR
>>> d
<__main__.PythonDerived object at 0xb7e688ec>
>>> d.Say()
'Python Say'
>>> d.CallSay()
C++ Say: 5
'Python Say'

0
投票

我找到了似乎可以解决此问题的解决方法。这有点“hacky”的感觉,所以如果有人有更好的解决方案,我们将不胜感激。

基本上我写了一个辅助类,所以 C++ 代码变成了:

#include <iostream>
#include <boost/python.hpp>
#include <boost/python/module.hpp>
#include <boost/python/class.hpp>
#include <boost/python/manage_new_object.hpp>
#include <boost/python/return_value_policy.hpp>

#define CREATE(NAME)                                \
  static inline std::shared_ptr<NAME> Create()      \
  {                                                 \
    std::cout << "STATIC BASE CREATE" << std::endl; \
    return std::make_shared<NAME>();                \
  }

class Base {
protected:

  Base()
  {
    std::cout << "BASE DEFAULT CONSTRUCTOR" << std::endl;
  }

public:

  std::string CallSay()
  {
    return Say();
  }

  virtual std::string Say() const = 0;
};

class BaseHelper {
public:

  BaseHelper() {}

  virtual std::string eval() = 0;
};

class BaseHelperWrap : public BaseHelper, public boost::python::wrapper<BaseHelper> {
public:

  BaseHelperWrap() : BaseHelper() {}

  virtual std::string eval() override
  {
    return this->get_override("eval") ();
  }
};

class BaseWrap : public Base, public boost::python::wrapper<Base> {
public:

  BaseWrap() : Base()
  {
    std::cout << "BASEWRAP DEFAULT CONSTRUCTOR" << std::endl;
  }

  virtual std::string Say() const override
  {
    std::cout << "C++ Say" << std::endl;

    return func->eval();
  }

  CREATE(BaseWrap) 

  static std::shared_ptr<BaseWrap> PyCreate(std::shared_ptr<BaseHelper> const& f)
  {
    std::shared_ptr<BaseWrap> ptr = Create();
    ptr->set_func(f);
    return ptr;
  }

private:

  void set_func(std::shared_ptr<BaseHelper> const& f)
  {
    func = f;
  }

  std::shared_ptr<BaseHelper> func;
};

BOOST_PYTHON_MODULE(Example)
{
  namespace python = boost::python;

  python::def("make_foo", make_foo, python::return_value_policy<python::manage_new_object>());

  // Expose Base.
  python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
  .def("Create", &BaseWrap::PyCreate).staticmethod("Create") 
  .def("CallSay", &Base::CallSay);


  python::class_<BaseHelperWrap, std::shared_ptr<BaseHelperWrap>, boost::noncopyable>("BaseHelper", python::init<>())
    .def("eval", python::pure_virtual(&BaseHelper::eval));

  python::implicitly_convertible<std::shared_ptr<BaseHelperWrap>, std::shared_ptr<BaseHelper> >();
}

和python代码:

import sys
import Example

class PyBaseHelper(Example.BaseHelper):
    def eval(self):
        return "Python Say"

h = PyBaseHelper()
d = Example.Base.Create(h)

print 
print
print d.CallSay()

它有效......但并不像我希望的那样优雅。


0
投票

我意识到这是一个将近十年的问题,但这是我的看法,以防有人通过使用自定义策略寻找优雅的解决方案。

template<typename HeldType, typename BasePolicies = default_call_policies, int iSelf = -1>
struct initialize_wrapper_policies : BasePolicies
{
    template<typename ArgumentPackage>
    static PyObject *postcall(const ArgumentPackage &args, PyObject *pResult)
    {
        PyObject *pSelf = boost::python::detail::get(boost::mpl::int_<iSelf>(), args);
        boost::python::detail::initialize_wrapper(
            pSelf,
            get_pointer((HeldType)extract<HeldType>(pSelf))
        );

        return BasePolicies::postcall(args, pResult);
    }
};

python::class_<BaseWrap, std::shared_ptr<BaseWrap>, boost::noncopyable>("Base", python::no_init)
    .def("__init__", 
        python::make_constructor(
            &BaseWrap::Create,
            initialize_wrapper_policies<std::shared_ptr<BaseWrap> >()
        )
    );
© www.soinside.com 2019 - 2024. All rights reserved.