如果您需要可修改的DenseMatrix 并且复制DenseMatrix 的成本很高,那么拥有将numpy.ndarray 转换为管理DenseMatrix 的可复制构造智能指针的转换器,例如boost::shared_ptr,可能是最好的选择。
Boost.Python 不支持custom lvalue converters。为了减少悬空引用的机会并在语言之间提供明确的方向性,Boost.Python 将通过 const 引用将转换产生的对象传递给函数。因此,当自定义转换器提供参数时,functions 参数必须通过值或 const 引用接受参数:
-
takeObject() 作为 DenseMatrix 参数将通过复制构造函数构造。
-
takeReference(DenseMatrix&) 失败,因为 DenseMatrix& 无法绑定到 const DenseMatrix&。
如果已经注册了将numpy.ndarray 转换为DenseMatrix 的自定义转换器,那么在将numpy.ndarray 注册到管理DenseMatrix 的智能指针时,可能可以重用大部分代码。
这是一个完整的示例,显示在两个自定义转换器之间共享的代码,这些转换器将 Python 对象包含一个 int x 和 y 属性到一个 Spam 对象。此外,该示例还展示了如何使用辅助函数通过非常量引用或指针传递转换后的对象。
#include <iostream>
#include <boost/make_shared.hpp>
#include <boost/python.hpp>
#include <boost/shared_ptr.hpp>
/// @brief Mockup Spam class.
struct Spam
{
int x;
int y;
Spam() { std::cout << "Spam()" << std::endl; }
~Spam() { std::cout << "~Spam()" << std::endl; }
Spam(const Spam& rhs) : x(rhs.x), y(rhs.y)
{ std::cout << "Spam(const Spam&)" << std::endl; }
};
/// @brief Helper function to ceck if an object has an attributed with a
/// specific type.
template <typename T>
bool hasattr(const boost::python::object& obj,
const char* name)
{
return PyObject_HasAttrString(obj.ptr(), name) &&
boost::python::extract<T>(obj.attr(name)).check();
}
/// @brief Helper type that provides conversions from a Python object to Spam.
struct spam_from_python
{
spam_from_python()
{
boost::python::converter::registry::push_back(
&spam_from_python::convertible,
&spam_from_python::construct,
boost::python::type_id<Spam>());
}
/// @brief Check if PyObject contains an x and y int attribute.
static void* convertible(PyObject* object)
{
namespace python = boost::python;
python::handle<> handle(python::borrowed(object));
python::object o(handle);
// If x and y are not int attributes, then return null.
if (!hasattr<int>(o, "x") && hasattr<int>(o, "y"))
return NULL;
return object;
}
/// @brief Convert PyObject to Spam.
static void construct(
PyObject* object,
boost::python::converter::rvalue_from_python_stage1_data* data)
{
// Obtain a handle to the memory block that the converter has allocated
// for the C++ type.
namespace python = boost::python;
typedef python::converter::rvalue_from_python_storage<Spam> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
// Allocate the C++ type into the converter's memory block, and assign
// its handle to the converter's convertible variable.
Spam* spam;
data->convertible = spam = new (storage) Spam();
// Initialize spam from an object.
initialize_spam(spam, object);
}
/// @brief Initialize a spam instance based on a python object.
static void initialize_spam(Spam* spam, PyObject* object)
{
namespace python = boost::python;
python::handle<> handle(python::borrowed(object));
python::object o(handle);
spam->x = python::extract<int>(o.attr("x"));
spam->y = python::extract<int>(o.attr("y"));
}
};
/// @brief Helper type that provides conversions from a Python object to
/// boost::shared_ptr<Spam>.
struct shared_spam_from_python
{
shared_spam_from_python()
{
boost::python::converter::registry::push_back(
&spam_from_python::convertible,
&shared_spam_from_python::construct,
boost::python::type_id<boost::shared_ptr<Spam> >());
}
/// @brief Convert PyObject to boost::shared<Spam>.
static void construct(
PyObject* object,
boost::python::converter::rvalue_from_python_stage1_data* data)
{
// Obtain a handle to the memory block that the converter has allocated
// for the C++ type.
namespace python = boost::python;
typedef python::converter::rvalue_from_python_storage<
boost::shared_ptr<Spam> > storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
// Allocate the C++ type into the converter's memory block, and assign
// its handle to the converter's convertible variable.
boost::shared_ptr<Spam>* spam;
data->convertible = spam =
new (storage) boost::shared_ptr<Spam>(boost::make_shared<Spam>());
// Initialize spam from an object.
spam_from_python::initialize_spam(spam->get(), object);
}
};
/// @brief Mockup functions acceping Spam in different ways.
void by_value(Spam spam) { std::cout << "by_value()" << std::endl; }
void by_const_ref(const Spam& spam) { std::cout << "by_cref()" << std::endl; }
void by_ref(Spam& spam) { std::cout << "by_ref()" << std::endl; }
void by_ptr(Spam* spam) { std::cout << "by_ptr()" << std::endl; }
/// @brief Use auxiliary functions that accept boost::shared_ptr<Spam> and
/// delegate to functions that have formal parameters of Spam& and
/// Spam*.
void by_ref_wrap(boost::shared_ptr<Spam> spam) { return by_ref(*spam); }
void by_ptr_wrap(boost::shared_ptr<Spam> spam) { return by_ptr(spam.get()); }
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Enable python to Spam conversion.
spam_from_python();
// Enable python to boost::shared_ptr<Spam> conversion.
shared_spam_from_python();
// Expose functions that have parameters that can accept a const Spam&
// argument.
python::def("by_value", &by_value);
python::def("by_const_ref", &by_const_ref);
// Expose functions that have parameters that can accept a const
// boost::shared_ptr<Spam>& argument. As copies of shared_ptr are cheap,
// a copy is used and the managed instance is passed to other functions,
// allowing Spam& and Spam* parameters.
python::def("by_ptr", &by_ptr_wrap);
python::def("by_ref", &by_ref_wrap);
}
互动使用:
>>> class Egg:
... x = 1
... y = 2
...
>>> import example
>>> example.by_value(Egg())
Spam()
Spam(const Spam&)
by_value()
~Spam()
~Spam()
>>> example.by_const_ref(Egg())
Spam()
by_cref()
~Spam()
>>> example.by_ref(Egg())
Spam()
by_ref()
~Spam()
>>> example.by_ptr(Egg())
Spam()
by_ptr()
~Spam()