从 numpy.uint8 数组中提取无符号字符

2024-01-30

我有代码从 python 序列中提取数值,并且在大多数情况下效果很好,但不适用于 numpy 数组。

当我尝试提取无符号字符时,我执行以下操作

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++ 中成功提取无符号字符?我是否必须为 numpy 类型编写/注册特殊转换器?我宁愿使用与其他 python 序列相同的代码,而不必编写使用PyArrayObject*.


人们可以使用 Boost.Python 注册一个自定义的 from-python 转换器来处理来自 NumPy 数组标量的转换,例如numpy.uint8,到 C++ 标量,例如unsigned char。自定义 from-python 转换器注册包含三个部分:

  • 一个函数,检查是否PyObject是可兑换的。返回NULL表明PyObject无法使用注册的转换器。
  • 从 a 构造 C++ 类型的构造函数PyObject。仅当以下情况时才会调用此函数converter(PyObject)不返回NULL.
  • 将构造的 C++ 类型。

从 NumPy 数组标量中提取值需要一些 NumPy C API 调用:

  • import_array() http://docs.scipy.org/doc/numpy/reference/c-api.array.html#import_array必须在将使用 NumPy C API 的扩展模块的初始化中调用。根据扩展使用 NumPy C API 的方式,可能需要满足其他导入要求。
  • PyArray_CheckScalar() http://docs.scipy.org/doc/numpy/reference/c-api.array.html#PyArray_CheckScalar检查是否PyObject是 NumPy 数组标量。
  • PyArray_DescrFromScalar() http://docs.scipy.org/doc/numpy/reference/c-api.array.html#PyArray_DescrFromScalar得到数据类型描述符 http://docs.scipy.org/doc/numpy/reference/c-api.types-and-structures.html#PyArray_Descr数组标量的对象。数据类型描述符对象包含有关如何解释底层字节的信息。例如,其type_num http://docs.scipy.org/doc/numpy/reference/c-api.types-and-structures.html#PyArray_Descr.type_num数据成员包含一个枚举值 http://docs.scipy.org/doc/numpy/reference/c-api.dtype.html#enumerated-types对应于C型。
  • PyArray_ScalarAsCtype() http://docs.scipy.org/doc/numpy/reference/c-api.array.html#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.Python能够提取boost::uint8_t来自两者numpy.uint8 and intPython 对象。
  • The enable_numpy_scalar_converter does not support promotions. For instance, it should be safe for test_specific_int32() to accept a numpy.int8 object that is promoted to a larger scalar type, such as int. If one wishes to perform promotions:
    • convertible()需要检查是否兼容NPY_TYPES
    • construct()应该使用PyArray_CastScalarToCtype() http://docs.scipy.org/doc/numpy/reference/c-api.array.html#PyArray_ScalarAsCtype将提取的数组标量值转换为所需的 C++ 类型。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从 numpy.uint8 数组中提取无符号字符 的相关文章

随机推荐