我正在尝试在 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实现。有谁知道为什么或如何使这项工作?
谢谢!
这看起来好像是 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'
我找到了似乎可以解决此问题的解决方法。这有点“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()
它有效......但并不像我希望的那样优雅。
我意识到这是一个将近十年的问题,但这是我的看法,以防有人通过使用自定义策略寻找优雅的解决方案。
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> >()
)
);