|
@@ -10,7 +10,10 @@
|
|
|
#pragma once
|
|
|
|
|
|
#include "pybind11.h"
|
|
|
+#include "detail/common.h"
|
|
|
#include "complex.h"
|
|
|
+#include "gil_safe_call_once.h"
|
|
|
+#include "pytypes.h"
|
|
|
|
|
|
#include <algorithm>
|
|
|
#include <array>
|
|
@@ -26,10 +29,15 @@
|
|
|
#include <utility>
|
|
|
#include <vector>
|
|
|
|
|
|
+#if defined(PYBIND11_NUMPY_1_ONLY) && !defined(PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED)
|
|
|
+# error PYBIND11_NUMPY_1_ONLY must be defined before any pybind11 header is included.
|
|
|
+#endif
|
|
|
+
|
|
|
/* This will be true on all flat address space platforms and allows us to reduce the
|
|
|
whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size
|
|
|
and dimension types (e.g. shape, strides, indexing), instead of inflicting this
|
|
|
- upon the library user. */
|
|
|
+ upon the library user.
|
|
|
+ Note that NumPy 2 now uses ssize_t for `npy_intp` to simplify this. */
|
|
|
static_assert(sizeof(::pybind11::ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t");
|
|
|
static_assert(std::is_signed<Py_intptr_t>::value, "Py_intptr_t must be signed");
|
|
|
// We now can reinterpret_cast between py::ssize_t and Py_intptr_t (MSVC + PyPy cares)
|
|
@@ -38,10 +46,16 @@ PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
|
|
|
|
|
|
PYBIND11_WARNING_DISABLE_MSVC(4127)
|
|
|
|
|
|
+class dtype; // Forward declaration
|
|
|
class array; // Forward declaration
|
|
|
|
|
|
PYBIND11_NAMESPACE_BEGIN(detail)
|
|
|
|
|
|
+template <>
|
|
|
+struct handle_type_name<dtype> {
|
|
|
+ static constexpr auto name = const_name("numpy.dtype");
|
|
|
+};
|
|
|
+
|
|
|
template <>
|
|
|
struct handle_type_name<array> {
|
|
|
static constexpr auto name = const_name("numpy.ndarray");
|
|
@@ -50,7 +64,8 @@ struct handle_type_name<array> {
|
|
|
template <typename type, typename SFINAE = void>
|
|
|
struct npy_format_descriptor;
|
|
|
|
|
|
-struct PyArrayDescr_Proxy {
|
|
|
+/* NumPy 1 proxy (always includes legacy fields) */
|
|
|
+struct PyArrayDescr1_Proxy {
|
|
|
PyObject_HEAD
|
|
|
PyObject *typeobj;
|
|
|
char kind;
|
|
@@ -65,6 +80,43 @@ struct PyArrayDescr_Proxy {
|
|
|
PyObject *names;
|
|
|
};
|
|
|
|
|
|
+#ifndef PYBIND11_NUMPY_1_ONLY
|
|
|
+struct PyArrayDescr_Proxy {
|
|
|
+ PyObject_HEAD
|
|
|
+ PyObject *typeobj;
|
|
|
+ char kind;
|
|
|
+ char type;
|
|
|
+ char byteorder;
|
|
|
+ char _former_flags;
|
|
|
+ int type_num;
|
|
|
+ /* Additional fields are NumPy version specific. */
|
|
|
+};
|
|
|
+#else
|
|
|
+/* NumPy 1.x only, we can expose all fields */
|
|
|
+using PyArrayDescr_Proxy = PyArrayDescr1_Proxy;
|
|
|
+#endif
|
|
|
+
|
|
|
+/* NumPy 2 proxy, including legacy fields */
|
|
|
+struct PyArrayDescr2_Proxy {
|
|
|
+ PyObject_HEAD
|
|
|
+ PyObject *typeobj;
|
|
|
+ char kind;
|
|
|
+ char type;
|
|
|
+ char byteorder;
|
|
|
+ char _former_flags;
|
|
|
+ int type_num;
|
|
|
+ std::uint64_t flags;
|
|
|
+ ssize_t elsize;
|
|
|
+ ssize_t alignment;
|
|
|
+ PyObject *metadata;
|
|
|
+ Py_hash_t hash;
|
|
|
+ void *reserved_null[2];
|
|
|
+ /* The following fields only exist if 0 <= type_num < 2056 */
|
|
|
+ char *subarray;
|
|
|
+ PyObject *fields;
|
|
|
+ PyObject *names;
|
|
|
+};
|
|
|
+
|
|
|
struct PyArray_Proxy {
|
|
|
PyObject_HEAD
|
|
|
char *data;
|
|
@@ -120,6 +172,28 @@ inline numpy_internals &get_numpy_internals() {
|
|
|
return *ptr;
|
|
|
}
|
|
|
|
|
|
+PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name) {
|
|
|
+ module_ numpy = module_::import("numpy");
|
|
|
+ str version_string = numpy.attr("__version__");
|
|
|
+
|
|
|
+ module_ numpy_lib = module_::import("numpy.lib");
|
|
|
+ object numpy_version = numpy_lib.attr("NumpyVersion")(version_string);
|
|
|
+ int major_version = numpy_version.attr("major").cast<int>();
|
|
|
+
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
+ if (major_version >= 2) {
|
|
|
+ throw std::runtime_error(
|
|
|
+ "This extension was built with PYBIND11_NUMPY_1_ONLY defined, "
|
|
|
+ "but NumPy 2 is used in this process. For NumPy2 compatibility, "
|
|
|
+ "this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define.");
|
|
|
+ }
|
|
|
+#endif
|
|
|
+ /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially
|
|
|
+ became a private module. */
|
|
|
+ std::string numpy_core_path = major_version >= 2 ? "numpy._core" : "numpy.core";
|
|
|
+ return module_::import((numpy_core_path + "." + submodule_name).c_str());
|
|
|
+}
|
|
|
+
|
|
|
template <typename T>
|
|
|
struct same_size {
|
|
|
template <typename U>
|
|
@@ -186,14 +260,16 @@ struct npy_api {
|
|
|
NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_),
|
|
|
};
|
|
|
|
|
|
+ unsigned int PyArray_RUNTIME_VERSION_;
|
|
|
+
|
|
|
struct PyArray_Dims {
|
|
|
Py_intptr_t *ptr;
|
|
|
int len;
|
|
|
};
|
|
|
|
|
|
static npy_api &get() {
|
|
|
- static npy_api api = lookup();
|
|
|
- return api;
|
|
|
+ PYBIND11_CONSTINIT static gil_safe_call_once_and_store<npy_api> storage;
|
|
|
+ return storage.call_once_and_store_result(lookup).get_stored();
|
|
|
}
|
|
|
|
|
|
bool PyArray_Check_(PyObject *obj) const {
|
|
@@ -224,6 +300,7 @@ struct npy_api {
|
|
|
PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *);
|
|
|
int (*PyArray_DescrConverter_)(PyObject *, PyObject **);
|
|
|
bool (*PyArray_EquivTypes_)(PyObject *, PyObject *);
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
int (*PyArray_GetArrayParamsFromObject_)(PyObject *,
|
|
|
PyObject *,
|
|
|
unsigned char,
|
|
@@ -232,6 +309,7 @@ struct npy_api {
|
|
|
Py_intptr_t *,
|
|
|
PyObject **,
|
|
|
PyObject *);
|
|
|
+#endif
|
|
|
PyObject *(*PyArray_Squeeze_)(PyObject *);
|
|
|
// Unused. Not removed because that affects ABI of the class.
|
|
|
int (*PyArray_SetBaseObject_)(PyObject *, PyObject *);
|
|
@@ -249,7 +327,8 @@ private:
|
|
|
API_PyArray_DescrFromScalar = 57,
|
|
|
API_PyArray_FromAny = 69,
|
|
|
API_PyArray_Resize = 80,
|
|
|
- API_PyArray_CopyInto = 82,
|
|
|
+ // CopyInto was slot 82 and 50 was effectively an alias. NumPy 2 removed 82.
|
|
|
+ API_PyArray_CopyInto = 50,
|
|
|
API_PyArray_NewCopy = 85,
|
|
|
API_PyArray_NewFromDescr = 94,
|
|
|
API_PyArray_DescrNewFromType = 96,
|
|
@@ -258,22 +337,29 @@ private:
|
|
|
API_PyArray_View = 137,
|
|
|
API_PyArray_DescrConverter = 174,
|
|
|
API_PyArray_EquivTypes = 182,
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
API_PyArray_GetArrayParamsFromObject = 278,
|
|
|
+#endif
|
|
|
API_PyArray_SetBaseObject = 282
|
|
|
};
|
|
|
|
|
|
static npy_api lookup() {
|
|
|
- module_ m = module_::import("numpy.core.multiarray");
|
|
|
+ module_ m = detail::import_numpy_core_submodule("multiarray");
|
|
|
auto c = m.attr("_ARRAY_API");
|
|
|
#if PY_MAJOR_VERSION >= 3
|
|
|
void **api_ptr = (void **) PyCapsule_GetPointer(c.ptr(), nullptr);
|
|
|
#else
|
|
|
void **api_ptr = (void **) PyCObject_AsVoidPtr(c.ptr());
|
|
|
#endif
|
|
|
+ if (api_ptr == nullptr) {
|
|
|
+ raise_from(PyExc_SystemError, "FAILURE obtaining numpy _ARRAY_API pointer.");
|
|
|
+ throw error_already_set();
|
|
|
+ }
|
|
|
npy_api api;
|
|
|
#define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func];
|
|
|
DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion);
|
|
|
- if (api.PyArray_GetNDArrayCFeatureVersion_() < 0x7) {
|
|
|
+ api.PyArray_RUNTIME_VERSION_ = api.PyArray_GetNDArrayCFeatureVersion_();
|
|
|
+ if (api.PyArray_RUNTIME_VERSION_ < 0x7) {
|
|
|
pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0");
|
|
|
}
|
|
|
DECL_NPY_API(PyArray_Type);
|
|
@@ -292,7 +378,9 @@ private:
|
|
|
DECL_NPY_API(PyArray_View);
|
|
|
DECL_NPY_API(PyArray_DescrConverter);
|
|
|
DECL_NPY_API(PyArray_EquivTypes);
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
DECL_NPY_API(PyArray_GetArrayParamsFromObject);
|
|
|
+#endif
|
|
|
DECL_NPY_API(PyArray_SetBaseObject);
|
|
|
|
|
|
#undef DECL_NPY_API
|
|
@@ -314,6 +402,14 @@ inline const PyArrayDescr_Proxy *array_descriptor_proxy(const PyObject *ptr) {
|
|
|
return reinterpret_cast<const PyArrayDescr_Proxy *>(ptr);
|
|
|
}
|
|
|
|
|
|
+inline const PyArrayDescr1_Proxy *array_descriptor1_proxy(const PyObject *ptr) {
|
|
|
+ return reinterpret_cast<const PyArrayDescr1_Proxy *>(ptr);
|
|
|
+}
|
|
|
+
|
|
|
+inline const PyArrayDescr2_Proxy *array_descriptor2_proxy(const PyObject *ptr) {
|
|
|
+ return reinterpret_cast<const PyArrayDescr2_Proxy *>(ptr);
|
|
|
+}
|
|
|
+
|
|
|
inline bool check_flags(const void *ptr, int flag) {
|
|
|
return (flag == (array_proxy(ptr)->flags & flag));
|
|
|
}
|
|
@@ -354,7 +450,7 @@ struct array_info<std::array<T, N>> {
|
|
|
}
|
|
|
|
|
|
static constexpr auto extents = const_name<array_info<T>::is_array>(
|
|
|
- concat(const_name<N>(), array_info<T>::extents), const_name<N>());
|
|
|
+ ::pybind11::detail::concat(const_name<N>(), array_info<T>::extents), const_name<N>());
|
|
|
};
|
|
|
// For numpy we have special handling for arrays of characters, so we don't include
|
|
|
// the size in the array extents.
|
|
@@ -593,10 +689,32 @@ public:
|
|
|
}
|
|
|
|
|
|
/// Size of the data type in bytes.
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; }
|
|
|
+#else
|
|
|
+ ssize_t itemsize() const {
|
|
|
+ if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
|
|
|
+ return detail::array_descriptor1_proxy(m_ptr)->elsize;
|
|
|
+ }
|
|
|
+ return detail::array_descriptor2_proxy(m_ptr)->elsize;
|
|
|
+ }
|
|
|
+#endif
|
|
|
|
|
|
/// Returns true for structured data types.
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; }
|
|
|
+#else
|
|
|
+ bool has_fields() const {
|
|
|
+ if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
|
|
|
+ return detail::array_descriptor1_proxy(m_ptr)->names != nullptr;
|
|
|
+ }
|
|
|
+ const auto *proxy = detail::array_descriptor2_proxy(m_ptr);
|
|
|
+ if (proxy->type_num < 0 || proxy->type_num >= 2056) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ return proxy->names != nullptr;
|
|
|
+ }
|
|
|
+#endif
|
|
|
|
|
|
/// Single-character code for dtype's kind.
|
|
|
/// For example, floating point types are 'f' and integral types are 'i'.
|
|
@@ -622,20 +740,39 @@ public:
|
|
|
/// Single character for byteorder
|
|
|
char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; }
|
|
|
|
|
|
- /// Alignment of the data type
|
|
|
+/// Alignment of the data type
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; }
|
|
|
+#else
|
|
|
+ ssize_t alignment() const {
|
|
|
+ if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
|
|
|
+ return detail::array_descriptor1_proxy(m_ptr)->alignment;
|
|
|
+ }
|
|
|
+ return detail::array_descriptor2_proxy(m_ptr)->alignment;
|
|
|
+ }
|
|
|
+#endif
|
|
|
|
|
|
- /// Flags for the array descriptor
|
|
|
+/// Flags for the array descriptor
|
|
|
+#ifdef PYBIND11_NUMPY_1_ONLY
|
|
|
char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; }
|
|
|
+#else
|
|
|
+ std::uint64_t flags() const {
|
|
|
+ if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) {
|
|
|
+ return (unsigned char) detail::array_descriptor1_proxy(m_ptr)->flags;
|
|
|
+ }
|
|
|
+ return detail::array_descriptor2_proxy(m_ptr)->flags;
|
|
|
+ }
|
|
|
+#endif
|
|
|
|
|
|
private:
|
|
|
- static object _dtype_from_pep3118() {
|
|
|
- static PyObject *obj = module_::import("numpy.core._internal")
|
|
|
- .attr("_dtype_from_pep3118")
|
|
|
- .cast<object>()
|
|
|
- .release()
|
|
|
- .ptr();
|
|
|
- return reinterpret_borrow<object>(obj);
|
|
|
+ static object &_dtype_from_pep3118() {
|
|
|
+ PYBIND11_CONSTINIT static gil_safe_call_once_and_store<object> storage;
|
|
|
+ return storage
|
|
|
+ .call_once_and_store_result([]() {
|
|
|
+ return detail::import_numpy_core_submodule("_internal")
|
|
|
+ .attr("_dtype_from_pep3118");
|
|
|
+ })
|
|
|
+ .get_stored();
|
|
|
}
|
|
|
|
|
|
dtype strip_padding(ssize_t itemsize) {
|
|
@@ -792,9 +929,7 @@ public:
|
|
|
}
|
|
|
|
|
|
/// Byte size of a single element
|
|
|
- ssize_t itemsize() const {
|
|
|
- return detail::array_descriptor_proxy(detail::array_proxy(m_ptr)->descr)->elsize;
|
|
|
- }
|
|
|
+ ssize_t itemsize() const { return dtype().itemsize(); }
|
|
|
|
|
|
/// Total number of bytes
|
|
|
ssize_t nbytes() const { return size() * itemsize(); }
|
|
@@ -1012,7 +1147,7 @@ protected:
|
|
|
/// Create array from any object -- always returns a new reference
|
|
|
static PyObject *raw_array(PyObject *ptr, int ExtraFlags = 0) {
|
|
|
if (ptr == nullptr) {
|
|
|
- PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
|
|
|
+ set_error(PyExc_ValueError, "cannot create a pybind11::array from a nullptr");
|
|
|
return nullptr;
|
|
|
}
|
|
|
return detail::npy_api::get().PyArray_FromAny_(
|
|
@@ -1159,7 +1294,7 @@ protected:
|
|
|
/// Create array from any object -- always returns a new reference
|
|
|
static PyObject *raw_array_t(PyObject *ptr) {
|
|
|
if (ptr == nullptr) {
|
|
|
- PyErr_SetString(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
|
|
|
+ set_error(PyExc_ValueError, "cannot create a pybind11::array_t from a nullptr");
|
|
|
return nullptr;
|
|
|
}
|
|
|
return detail::npy_api::get().PyArray_FromAny_(ptr,
|