我有从python序列中提取数值的代码,它在大多数情况下运行良好,但对于numpy数组则不行。
当我尝试提取unsigned char时,我会执行以下操作
unsigned char val = boost::python::extract<unsigned char>(sequence[n]);
其中sequence是任何python序列,n是索引。 我收到以下错误:
TypeError: No registered converter was able to produce a C++ rvalue of type
unsigned char from this Python object of type numpy.uint8
如何在C ++中成功提取unsigned char?我是否必须为numpy类型编写/注册特殊转换器?我宁愿使用与其他python序列相同的代码,也不必编写使用PyArrayObject*
的特殊代码。
答案 0 :(得分:5)
可以使用Boost.Python注册自定义from-python转换器,以处理从NumPy数组标量(例如numpy.uint8
)到C ++标量(例如unsigned char
)的转换。自定义from-python转换器注册有三个部分:
PyObject
是否可转换的函数。返回NULL
表示PyObject
无法使用已注册的转换器。PyObject
构造C ++类型。仅当converter(PyObject)
未返回NULL
时才会调用此函数。从NumPy数组标量中提取值需要几个NumPy C API调用:
import_array()
。根据扩展程序使用NumPy C API的方式,可能需要进行其他导入要求。PyArray_CheckScalar()
检查PyObject
是否为NumPy数组标量。PyArray_DescrFromScalar()
获取数组标量的data-type-descriptor对象。 data-type-descriptor对象包含有关如何解释基础字节的信息。例如,其type_num
数据成员包含与C类型对应的enum value。PyArray_ScalarAsCtype()
可用于从NumPy数组标量中提取C类型值。这是一个完整的示例,演示如何使用辅助类enable_numpy_scalar_converter
将特定的NumPy数组标量注册到相应的C ++类型。
#include <boost/cstdint.hpp>
#include <boost/python.hpp>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>
// Mockup functions.
/// @brief Mockup function that will explicitly extract a uint8_t
/// from the Boost.Python object.
boost::uint8_t test_generic_uint8(boost::python::object object)
{
return boost::python::extract<boost::uint8_t>(object)();
}
/// @brief Mockup function that uses automatic conversions for uint8_t.
boost::uint8_t test_specific_uint8(boost::uint8_t value) { return value; }
/// @brief Mokcup function that uses automatic conversions for int32_t.
boost::int32_t test_specific_int32(boost::int32_t value) { return value; }
/// @brief Converter type that enables automatic conversions between NumPy
/// scalars and C++ types.
template <typename T, NPY_TYPES NumPyScalarType>
struct enable_numpy_scalar_converter
{
enable_numpy_scalar_converter()
{
// Required NumPy call in order to use the NumPy C API within another
// extension module.
import_array();
boost::python::converter::registry::push_back(
&convertible,
&construct,
boost::python::type_id<T>());
}
static void* convertible(PyObject* object)
{
// The object is convertible if all of the following are true:
// - is a valid object.
// - is a numpy array scalar.
// - its descriptor type matches the type for this converter.
return (
object && // Valid
PyArray_CheckScalar(object) && // Scalar
PyArray_DescrFromScalar(object)->type_num == NumPyScalarType // Match
)
? object // The Python object can be converted.
: NULL;
}
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<T> storage_type;
void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;
// Extract the array scalar type directly into the storage.
PyArray_ScalarAsCtype(object, storage);
// Set convertible to indicate success.
data->convertible = storage;
}
};
BOOST_PYTHON_MODULE(example)
{
namespace python = boost::python;
// Enable numpy scalar conversions.
enable_numpy_scalar_converter<boost::uint8_t, NPY_UBYTE>();
enable_numpy_scalar_converter<boost::int32_t, NPY_INT>();
// Expose test functions.
python::def("test_generic_uint8", &test_generic_uint8);
python::def("test_specific_uint8", &test_specific_uint8);
python::def("test_specific_int32", &test_specific_int32);
}
交互式使用:
>>> import numpy
>>> import example
>>> assert(42 == example.test_generic_uint8(42))
>>> assert(42 == example.test_generic_uint8(numpy.uint8(42)))
>>> assert(42 == example.test_specific_uint8(42))
>>> assert(42 == example.test_specific_uint8(numpy.uint8(42)))
>>> assert(42 == example.test_specific_int32(numpy.int32(42)))
>>> example.test_specific_int32(numpy.int8(42))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
Boost.Python.ArgumentError: Python argument types in
example.test_specific_int32(numpy.int8)
did not match C++ signature:
test_specific_int32(int)
>>> example.test_generic_uint8(numpy.int8(42))
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: No registered converter was able to produce a C++ rvalue of type
unsigned char from this Python object of type numpy.int8
交互式使用需要注意的一些事项:
boost::uint8_t
和numpy.uint8
Python对象中提取int
。enable_numpy_scalar_converter
不支持促销活动。例如,test_specific_int32()
接受被提升为更大标量类型的numpy.int8
对象应该是安全的,例如int
。如果有人希望进行促销活动:
convertible()
需要检查兼容的NPY_TYPES
construct()
应使用PyArray_CastScalarToCtype()
将提取的数组标量值转换为所需的C ++类型。答案 1 :(得分:1)
以下是接受答案的稍微更通用的版本:
https://github.com/stuarteberg/printnum
(转换器是从VIGRA C ++ / Python绑定中复制的。)
接受的答案指出它不支持在标量类型之间进行转换。此转换器将允许任意两种标量类型之间的隐式转换(即,int32
至int8
或float32
至uint8
)。我认为这通常更好,但在这里做了一些轻微的便利/安全权衡。
#include <iostream>
#include <boost/python.hpp>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
// http://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api
#define PY_ARRAY_UNIQUE_SYMBOL printnum_cpp_module_PyArray_API
#include <numpy/arrayobject.h>
#include <numpy/arrayscalars.h>
/*
* Boost python converter for numpy scalars, e.g. numpy.uint32(123).
* Enables automatic conversion from numpy.intXX, floatXX
* in python to C++ char, short, int, float, etc.
* When casting from float to int (or wide int to narrow int),
* normal C++ casting rules apply.
*
* Like all boost::python converters, this enables automatic conversion for function args
* exposed via boost::python::def(), as well as values converted via boost::python::extract<>().
*
* Copied from the VIGRA C++ library source code (MIT license).
* http://ukoethe.github.io/vigra
* https://github.com/ukoethe/vigra
*/
template <typename ScalarType>
struct NumpyScalarConverter
{
NumpyScalarConverter()
{
using namespace boost::python;
converter::registry::push_back( &convertible, &construct, type_id<ScalarType>());
}
// Determine if obj_ptr is a supported numpy.number
static void* convertible(PyObject* obj_ptr)
{
if (PyArray_IsScalar(obj_ptr, Float32) ||
PyArray_IsScalar(obj_ptr, Float64) ||
PyArray_IsScalar(obj_ptr, Int8) ||
PyArray_IsScalar(obj_ptr, Int16) ||
PyArray_IsScalar(obj_ptr, Int32) ||
PyArray_IsScalar(obj_ptr, Int64) ||
PyArray_IsScalar(obj_ptr, UInt8) ||
PyArray_IsScalar(obj_ptr, UInt16) ||
PyArray_IsScalar(obj_ptr, UInt32) ||
PyArray_IsScalar(obj_ptr, UInt64))
{
return obj_ptr;
}
return 0;
}
static void construct( PyObject* obj_ptr, boost::python::converter::rvalue_from_python_stage1_data* data)
{
using namespace boost::python;
// Grab pointer to memory into which to construct the C++ scalar
void* storage = ((converter::rvalue_from_python_storage<ScalarType>*) data)->storage.bytes;
// in-place construct the new scalar value
ScalarType * scalar = new (storage) ScalarType;
if (PyArray_IsScalar(obj_ptr, Float32))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Float32);
else if (PyArray_IsScalar(obj_ptr, Float64))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Float64);
else if (PyArray_IsScalar(obj_ptr, Int8))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Int8);
else if (PyArray_IsScalar(obj_ptr, Int16))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Int16);
else if (PyArray_IsScalar(obj_ptr, Int32))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Int32);
else if (PyArray_IsScalar(obj_ptr, Int64))
(*scalar) = PyArrayScalar_VAL(obj_ptr, Int64);
else if (PyArray_IsScalar(obj_ptr, UInt8))
(*scalar) = PyArrayScalar_VAL(obj_ptr, UInt8);
else if (PyArray_IsScalar(obj_ptr, UInt16))
(*scalar) = PyArrayScalar_VAL(obj_ptr, UInt16);
else if (PyArray_IsScalar(obj_ptr, UInt32))
(*scalar) = PyArrayScalar_VAL(obj_ptr, UInt32);
else if (PyArray_IsScalar(obj_ptr, UInt64))
(*scalar) = PyArrayScalar_VAL(obj_ptr, UInt64);
// Stash the memory chunk pointer for later use by boost.python
data->convertible = storage;
}
};
/*
* A silly function to test scalar conversion.
* The first arg tests automatic function argument conversion.
* The second arg is used to demonstrate explicit conversion via boost::python::extract<>()
*/
void print_number( uint32_t number, boost::python::object other_number )
{
using namespace boost::python;
std::cout << "The number is: " << number << std::endl;
std::cout << "The other number is: " << extract<int16_t>(other_number) << std::endl;
}
/*
* Instantiate the python extension module 'printnum'.
*
* Example Python usage:
*
* import numpy as np
* from printnum import print_number
* print_number( np.uint8(123), np.int64(-456) )
*
* ## That prints the following:
* # The number is: 123
* # The other number is: -456
*/
BOOST_PYTHON_MODULE(printnum)
{
using namespace boost::python;
// http://docs.scipy.org/doc/numpy/reference/c-api.array.html#importing-the-api
import_array();
// Register conversion for all scalar types.
NumpyScalarConverter<signed char>();
NumpyScalarConverter<short>();
NumpyScalarConverter<int>();
NumpyScalarConverter<long>();
NumpyScalarConverter<long long>();
NumpyScalarConverter<unsigned char>();
NumpyScalarConverter<unsigned short>();
NumpyScalarConverter<unsigned int>();
NumpyScalarConverter<unsigned long>();
NumpyScalarConverter<unsigned long long>();
NumpyScalarConverter<float>();
NumpyScalarConverter<double>();
// Expose our C++ function as a python function.
def("print_number", &print_number, (arg("number"), arg("other_number")));
}