summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--numpy/core/src/multiarray/array_coercion.c164
-rw-r--r--numpy/core/src/multiarray/array_coercion.h10
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c5
-rw-r--r--numpy/core/src/multiarray/convert_datatype.h2
-rw-r--r--numpy/core/src/multiarray/ctors.c3
-rw-r--r--numpy/core/src/multiarray/descriptor.c135
-rw-r--r--numpy/core/src/multiarray/descriptor.h23
-rw-r--r--numpy/core/src/multiarray/methods.c15
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c6
-rw-r--r--numpy/core/src/multiarray/nditer_constr.c2
-rw-r--r--numpy/core/tests/test_array_coercion.py2
-rw-r--r--numpy/core/tests/test_custom_dtypes.py10
12 files changed, 243 insertions, 134 deletions
diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c
index f77a4a898..d6bee1a7b 100644
--- a/numpy/core/src/multiarray/array_coercion.c
+++ b/numpy/core/src/multiarray/array_coercion.c
@@ -16,6 +16,7 @@
#include "common_dtype.h"
#include "dtypemeta.h"
+#include "npy_argparse.h"
#include "abstractdtypes.h"
#include "array_coercion.h"
#include "ctors.h"
@@ -873,42 +874,61 @@ find_descriptor_from_array(
/**
* Given a dtype or DType object, find the correct descriptor to cast the
- * array to.
+ * array to. In some places, this function is used with dtype=NULL which
+ * means that legacy behavior is used: The dtype instances "S0", "U0", and
+ * "V0" are converted to mean the DType classes instead.
+ * When dtype != NULL, this path is ignored, and the function does nothing
+ * unless descr == NULL.
*
* This function is identical to normal casting using only the dtype, however,
* it supports inspecting the elements when the array has object dtype
* (and the given datatype describes a parametric DType class).
*
* @param arr
- * @param dtype A dtype instance or class.
+ * @param dtype NULL or a dtype class
+ * @param descr A dtype instance, if the dtype is NULL the dtype class is
+ * found and e.g. "S0" is converted to denote only String.
* @return A concrete dtype instance or NULL
*/
NPY_NO_EXPORT PyArray_Descr *
-PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype)
+PyArray_AdaptDescriptorToArray(
+ PyArrayObject *arr, PyArray_DTypeMeta *dtype, PyArray_Descr *descr)
{
/* If the requested dtype is flexible, adapt it */
- PyArray_Descr *new_dtype;
- PyArray_DTypeMeta *new_DType;
+ PyArray_Descr *new_descr;
int res;
- res = PyArray_ExtractDTypeAndDescriptor((PyObject *)dtype,
- &new_dtype, &new_DType);
- if (res < 0) {
- return NULL;
+ if (dtype != NULL && descr != NULL) {
+ /* descr was given and no special logic, return (call not necessary) */
+ Py_INCREF(descr);
+ return descr;
}
- if (new_dtype == NULL) {
- res = find_descriptor_from_array(arr, new_DType, &new_dtype);
+ if (dtype == NULL) {
+ res = PyArray_ExtractDTypeAndDescriptor(descr, &new_descr, &dtype);
if (res < 0) {
- Py_DECREF(new_DType);
return NULL;
}
- if (new_dtype == NULL) {
- /* This is an object array but contained no elements, use default */
- new_dtype = NPY_DT_CALL_default_descr(new_DType);
+ if (new_descr != NULL) {
+ Py_DECREF(dtype);
+ return new_descr;
}
}
- Py_DECREF(new_DType);
- return new_dtype;
+ else {
+ assert(descr == NULL); /* gueranteed above */
+ Py_INCREF(dtype);
+ }
+
+ res = find_descriptor_from_array(arr, dtype, &new_descr);
+ if (res < 0) {
+ Py_DECREF(dtype);
+ return NULL;
+ }
+ if (new_descr == NULL) {
+ /* This is an object array but contained no elements, use default */
+ new_descr = NPY_DT_CALL_default_descr(dtype);
+ }
+ Py_DECREF(dtype);
+ return new_descr;
}
@@ -1380,111 +1400,25 @@ PyArray_DiscoverDTypeAndShape(
}
-
-/**
- * Check the descriptor is a legacy "flexible" DType instance, this is
- * an instance which is (normally) not attached to an array, such as a string
- * of length 0 or a datetime with no unit.
- * These should be largely deprecated, and represent only the DType class
- * for most `dtype` parameters.
- *
- * TODO: This function should eventually receive a deprecation warning and
- * be removed.
- *
- * @param descr
- * @return 1 if this is not a concrete dtype instance 0 otherwise
- */
-static int
-descr_is_legacy_parametric_instance(PyArray_Descr *descr,
- PyArray_DTypeMeta *DType)
-{
- if (!NPY_DT_is_legacy(DType)) {
- return 0;
- }
-
- if (PyDataType_ISUNSIZED(descr)) {
- return 1;
- }
- /* Flexible descr with generic time unit (which can be adapted) */
- if (PyDataType_ISDATETIME(descr)) {
- PyArray_DatetimeMetaData *meta;
- meta = get_datetime_metadata_from_dtype(descr);
- if (meta->base == NPY_FR_GENERIC) {
- return 1;
- }
- }
- return 0;
-}
-
-
-/**
- * Given either a DType instance or class, (or legacy flexible instance),
- * ands sets output dtype instance and DType class. Both results may be
- * NULL, but if `out_descr` is set `out_DType` will always be the
- * corresponding class.
- *
- * @param dtype
- * @param out_descr
- * @param out_DType
- * @return 0 on success -1 on failure
- */
-NPY_NO_EXPORT int
-PyArray_ExtractDTypeAndDescriptor(PyObject *dtype,
- PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType)
-{
- *out_DType = NULL;
- *out_descr = NULL;
-
- if (dtype != NULL) {
- if (PyObject_TypeCheck(dtype, (PyTypeObject *)&PyArrayDTypeMeta_Type)) {
- assert(dtype != (PyObject * )&PyArrayDescr_Type); /* not np.dtype */
- *out_DType = (PyArray_DTypeMeta *)dtype;
- Py_INCREF(*out_DType);
- }
- else if (PyObject_TypeCheck((PyObject *)Py_TYPE(dtype),
- (PyTypeObject *)&PyArrayDTypeMeta_Type)) {
- *out_DType = NPY_DTYPE(dtype);
- Py_INCREF(*out_DType);
- if (!descr_is_legacy_parametric_instance((PyArray_Descr *)dtype,
- *out_DType)) {
- *out_descr = (PyArray_Descr *)dtype;
- Py_INCREF(*out_descr);
- }
- }
- else {
- PyErr_SetString(PyExc_TypeError,
- "dtype parameter must be a DType instance or class.");
- return -1;
- }
- }
- return 0;
-}
-
-
/*
* Python API function to expose the dtype+shape discovery functionality
* directly.
*/
NPY_NO_EXPORT PyObject *
_discover_array_parameters(PyObject *NPY_UNUSED(self),
- PyObject *args, PyObject *kwargs)
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- static char *kwlist[] = {"obj", "dtype", NULL};
-
PyObject *obj;
- PyObject *dtype = NULL;
- PyArray_Descr *fixed_descriptor = NULL;
- PyArray_DTypeMeta *fixed_DType = NULL;
+ npy_dtype_info dt_info = {NULL, NULL};
npy_intp shape[NPY_MAXDIMS];
- if (!PyArg_ParseTupleAndKeywords(
- args, kwargs, "O|O:_discover_array_parameters", kwlist,
- &obj, &dtype)) {
- return NULL;
- }
-
- if (PyArray_ExtractDTypeAndDescriptor(dtype,
- &fixed_descriptor, &fixed_DType) < 0) {
+ NPY_PREPARE_ARGPARSER;
+ if (npy_parse_arguments(
+ "_discover_array_parameters", args, len_args, kwnames,
+ "", NULL, &obj,
+ "|dtype", &PyArray_DTypeOrDescrConverterOptional, &dt_info,
+ NULL, NULL, NULL) < 0) {
+ /* fixed is last to parse, so never necessary to clean up */
return NULL;
}
@@ -1493,9 +1427,9 @@ _discover_array_parameters(PyObject *NPY_UNUSED(self),
int ndim = PyArray_DiscoverDTypeAndShape(
obj, NPY_MAXDIMS, shape,
&coercion_cache,
- fixed_DType, fixed_descriptor, (PyArray_Descr **)&out_dtype, 0);
- Py_XDECREF(fixed_DType);
- Py_XDECREF(fixed_descriptor);
+ dt_info.dtype, dt_info.descr, (PyArray_Descr **)&out_dtype, 0);
+ Py_XDECREF(dt_info.dtype);
+ Py_XDECREF(dt_info.descr);
if (ndim < 0) {
return NULL;
}
diff --git a/numpy/core/src/multiarray/array_coercion.h b/numpy/core/src/multiarray/array_coercion.h
index 63d543cf7..0757c1cb8 100644
--- a/numpy/core/src/multiarray/array_coercion.h
+++ b/numpy/core/src/multiarray/array_coercion.h
@@ -26,7 +26,8 @@ NPY_NO_EXPORT int
PyArray_Pack(PyArray_Descr *descr, char *item, PyObject *value);
NPY_NO_EXPORT PyArray_Descr *
-PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype);
+PyArray_AdaptDescriptorToArray(
+ PyArrayObject *arr, PyArray_DTypeMeta *dtype, PyArray_Descr *descr);
NPY_NO_EXPORT int
PyArray_DiscoverDTypeAndShape(
@@ -36,14 +37,9 @@ PyArray_DiscoverDTypeAndShape(
PyArray_DTypeMeta *fixed_DType, PyArray_Descr *requested_descr,
PyArray_Descr **out_descr, int never_copy);
-NPY_NO_EXPORT int
-PyArray_ExtractDTypeAndDescriptor(PyObject *dtype,
- PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType);
-
NPY_NO_EXPORT PyObject *
_discover_array_parameters(PyObject *NPY_UNUSED(self),
- PyObject *args, PyObject *kwargs);
-
+ PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames);
/* Would make sense to inline the freeing functions everywhere */
/* Frees the coercion cache object recursively. */
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 67c032cc7..53db5c577 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -18,6 +18,7 @@
#include "can_cast_table.h"
#include "common.h"
#include "ctors.h"
+#include "descriptor.h"
#include "dtypemeta.h"
#include "common_dtype.h"
#include "scalartypes.h"
@@ -330,7 +331,7 @@ PyArray_CastToType(PyArrayObject *arr, PyArray_Descr *dtype, int is_f_order)
return NULL;
}
- Py_SETREF(dtype, PyArray_AdaptDescriptorToArray(arr, (PyObject *)dtype));
+ Py_SETREF(dtype, PyArray_AdaptDescriptorToArray(arr, NULL, dtype));
if (dtype == NULL) {
return NULL;
}
@@ -1149,7 +1150,7 @@ PyArray_CastDescrToDType(PyArray_Descr *descr, PyArray_DTypeMeta *given_DType)
*/
NPY_NO_EXPORT PyArray_Descr *
PyArray_FindConcatenationDescriptor(
- npy_intp n, PyArrayObject **arrays, PyObject *requested_dtype)
+ npy_intp n, PyArrayObject **arrays, PyArray_Descr *requested_dtype)
{
if (requested_dtype == NULL) {
return PyArray_ResultType(n, arrays, 0, NULL);
diff --git a/numpy/core/src/multiarray/convert_datatype.h b/numpy/core/src/multiarray/convert_datatype.h
index 1a23965f8..a68c669ee 100644
--- a/numpy/core/src/multiarray/convert_datatype.h
+++ b/numpy/core/src/multiarray/convert_datatype.h
@@ -82,7 +82,7 @@ PyArray_CastDescrToDType(PyArray_Descr *descr, PyArray_DTypeMeta *given_DType);
NPY_NO_EXPORT PyArray_Descr *
PyArray_FindConcatenationDescriptor(
- npy_intp n, PyArrayObject **arrays, PyObject *requested_dtype);
+ npy_intp n, PyArrayObject **arrays, PyArray_Descr *requested_dtype);
NPY_NO_EXPORT int
PyArray_AddCastingImplementation(PyBoundArrayMethodObject *meth);
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index fa3b103dd..38af60427 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -20,6 +20,7 @@
#include "common.h"
#include "ctors.h"
#include "convert_datatype.h"
+#include "descriptor.h"
#include "dtypemeta.h"
#include "shape.h"
#include "npy_buffer.h"
@@ -1621,7 +1622,7 @@ PyArray_FromAny(PyObject *op, PyArray_Descr *newtype, int min_depth,
PyArray_Descr *fixed_descriptor;
PyArray_DTypeMeta *fixed_DType;
- if (PyArray_ExtractDTypeAndDescriptor((PyObject *)newtype,
+ if (PyArray_ExtractDTypeAndDescriptor(newtype,
&fixed_descriptor, &fixed_DType) < 0) {
Py_XDECREF(newtype);
return NULL;
diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c
index ac8aeef1a..c4f37ee18 100644
--- a/numpy/core/src/multiarray/descriptor.c
+++ b/numpy/core/src/multiarray/descriptor.c
@@ -1390,6 +1390,141 @@ PyArray_DescrConverter2(PyObject *obj, PyArray_Descr **at)
}
}
+
+/**
+ * Check the descriptor is a legacy "flexible" DType instance, this is
+ * an instance which is (normally) not attached to an array, such as a string
+ * of length 0 or a datetime with no unit.
+ * These should be largely deprecated, and represent only the DType class
+ * for most `dtype` parameters.
+ *
+ * TODO: This function should eventually receive a deprecation warning and
+ * be removed.
+ *
+ * @param descr
+ * @return 1 if this is not a concrete dtype instance 0 otherwise
+ */
+static int
+descr_is_legacy_parametric_instance(PyArray_Descr *descr,
+ PyArray_DTypeMeta *DType)
+{
+ if (!NPY_DT_is_legacy(DType)) {
+ return 0;
+ }
+
+ if (PyDataType_ISUNSIZED(descr)) {
+ return 1;
+ }
+ /* Flexible descr with generic time unit (which can be adapted) */
+ if (PyDataType_ISDATETIME(descr)) {
+ PyArray_DatetimeMetaData *meta;
+ meta = get_datetime_metadata_from_dtype(descr);
+ if (meta->base == NPY_FR_GENERIC) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Given a descriptor (dtype instance), handles conversion of legacy flexible
+ * "unsized" descriptors to their DType. It returns the DType and descriptor
+ * both results can be NULL (if the input is). But it always sets the DType
+ * when a descriptor is set.
+ *
+ * @param dtype
+ * @param out_descr
+ * @param out_DType
+ * @return 0 on success -1 on failure
+ */
+NPY_NO_EXPORT int
+PyArray_ExtractDTypeAndDescriptor(PyArray_Descr *dtype,
+ PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType)
+{
+ *out_DType = NULL;
+ *out_descr = NULL;
+
+ if (dtype != NULL) {
+ *out_DType = NPY_DTYPE(dtype);
+ Py_INCREF(*out_DType);
+ if (!descr_is_legacy_parametric_instance((PyArray_Descr *)dtype,
+ *out_DType)) {
+ *out_descr = (PyArray_Descr *)dtype;
+ Py_INCREF(*out_descr);
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Converter function filling in an npy_dtype_info struct on success.
+ *
+ * @param obj representing a dtype instance (descriptor) or DType class.
+ * @param[out] npy_dtype_info filled with the DType class and dtype/descriptor
+ * instance. The class is always set while the instance may be NULL.
+ * On error, both will be NULL.
+ * @return 0 on failure and 1 on success (as a converter)
+ */
+NPY_NO_EXPORT int
+PyArray_DTypeOrDescrConverterRequired(PyObject *obj, npy_dtype_info *dt_info)
+{
+ /*
+ * Allow dtype classes pass, this could also be generalized to at least
+ * some scalar types (right now most of these give instances or)
+ */
+ dt_info->dtype = NULL;
+ dt_info->descr = NULL;
+
+ if (PyObject_TypeCheck(obj, &PyArrayDTypeMeta_Type)) {
+ Py_INCREF(obj);
+ dt_info->dtype = (PyArray_DTypeMeta *)obj;
+ dt_info->descr = NULL;
+ return NPY_SUCCEED;
+ }
+ PyArray_Descr *descr;
+ if (PyArray_DescrConverter(obj, &descr) != NPY_SUCCEED) {
+ return NPY_FAIL;
+ }
+ /*
+ * The above converts e.g. "S" or "S0" to the prototype instance, we make
+ * it behave the same as the DType. This is not fully correct, "S0" should
+ * be considered an instance with actual 0 length.
+ * TODO: It would be nice to fix that eventually.
+ */
+ int res = PyArray_ExtractDTypeAndDescriptor(
+ descr, &dt_info->descr, &dt_info->dtype);
+ Py_DECREF(descr);
+ if (res < 0) {
+ return NPY_FAIL;
+ }
+ return NPY_SUCCEED;
+}
+
+
+/**
+ * Converter function filling in an npy_dtype_info struct on success. It
+ * accepts `None` and does nothing in that case (user must initialize to
+ * NULL anyway).
+ *
+ * @param obj None or obj representing a dtype instance (descr) or DType class.
+ * @param[out] npy_dtype_info filled with the DType class and dtype/descriptor
+ * instance. If `obj` is None, is not modified. Otherwise the class
+ * is always set while the instance may be NULL.
+ * On error, both will be NULL.
+ * @return 0 on failure and 1 on success (as a converter)
+ */
+NPY_NO_EXPORT int
+PyArray_DTypeOrDescrConverterOptional(PyObject *obj, npy_dtype_info *dt_info)
+{
+ if (obj == Py_None) {
+ /* caller must have initialized for the optional version */
+ return NPY_SUCCEED;
+ }
+ return PyArray_DTypeOrDescrConverterRequired(obj, dt_info);
+}
+
/**
* Get a dtype instance from a python type
*/
diff --git a/numpy/core/src/multiarray/descriptor.h b/numpy/core/src/multiarray/descriptor.h
index 7e6f212f2..b14edc3aa 100644
--- a/numpy/core/src/multiarray/descriptor.h
+++ b/numpy/core/src/multiarray/descriptor.h
@@ -1,6 +1,29 @@
#ifndef NUMPY_CORE_SRC_MULTIARRAY_DESCRIPTOR_H_
#define NUMPY_CORE_SRC_MULTIARRAY_DESCRIPTOR_H_
+
+/*
+ * In some API calls we wish to allow users to pass a DType class or a
+ * dtype instances with different meanings.
+ * This struct is mainly used for the argument parsing in
+ * `PyArray_DTypeOrDescrConverter`.
+ */
+typedef struct {
+ PyArray_DTypeMeta *dtype;
+ PyArray_Descr *descr;
+} npy_dtype_info;
+
+
+NPY_NO_EXPORT int
+PyArray_DTypeOrDescrConverterOptional(PyObject *, npy_dtype_info *dt_info);
+
+NPY_NO_EXPORT int
+PyArray_DTypeOrDescrConverterRequired(PyObject *, npy_dtype_info *dt_info);
+
+NPY_NO_EXPORT int
+PyArray_ExtractDTypeAndDescriptor(PyArray_Descr *dtype,
+ PyArray_Descr **out_descr, PyArray_DTypeMeta **out_DType);
+
NPY_NO_EXPORT PyObject *arraydescr_protocol_typestr_get(
PyArray_Descr *, void *);
NPY_NO_EXPORT PyObject *arraydescr_protocol_descr_get(
diff --git a/numpy/core/src/multiarray/methods.c b/numpy/core/src/multiarray/methods.c
index 56225eb52..f518f3a02 100644
--- a/numpy/core/src/multiarray/methods.c
+++ b/numpy/core/src/multiarray/methods.c
@@ -21,6 +21,7 @@
#include "ctors.h"
#include "calculation.h"
#include "convert_datatype.h"
+#include "descriptor.h"
#include "item_selection.h"
#include "conversion_utils.h"
#include "shape.h"
@@ -851,29 +852,35 @@ static PyObject *
array_astype(PyArrayObject *self,
PyObject *const *args, Py_ssize_t len_args, PyObject *kwnames)
{
- PyArray_Descr *dtype = NULL;
/*
* TODO: UNSAFE default for compatibility, I think
* switching to SAME_KIND by default would be good.
*/
+ npy_dtype_info dt_info;
NPY_CASTING casting = NPY_UNSAFE_CASTING;
NPY_ORDER order = NPY_KEEPORDER;
_PyArray_CopyMode forcecopy = 1;
int subok = 1;
+
NPY_PREPARE_ARGPARSER;
if (npy_parse_arguments("astype", args, len_args, kwnames,
- "dtype", &PyArray_DescrConverter, &dtype,
+ "dtype", &PyArray_DTypeOrDescrConverterRequired, &dt_info,
"|order", &PyArray_OrderConverter, &order,
"|casting", &PyArray_CastingConverter, &casting,
"|subok", &PyArray_PythonPyIntFromInt, &subok,
"|copy", &PyArray_CopyConverter, &forcecopy,
NULL, NULL, NULL) < 0) {
- Py_XDECREF(dtype);
+ Py_XDECREF(dt_info.descr);
+ Py_XDECREF(dt_info.dtype);
return NULL;
}
/* If it is not a concrete dtype instance find the best one for the array */
- Py_SETREF(dtype, PyArray_AdaptDescriptorToArray(self, (PyObject *)dtype));
+ PyArray_Descr *dtype;
+
+ dtype = PyArray_AdaptDescriptorToArray(self, dt_info.dtype, dt_info.descr);
+ Py_XDECREF(dt_info.descr);
+ Py_DECREF(dt_info.dtype);
if (dtype == NULL) {
return NULL;
}
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index 4fa58c4df..e85f8affa 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -473,7 +473,7 @@ PyArray_ConcatenateArrays(int narrays, PyArrayObject **arrays, int axis,
/* Get the priority subtype for the array */
PyTypeObject *subtype = PyArray_GetSubType(narrays, arrays);
PyArray_Descr *descr = PyArray_FindConcatenationDescriptor(
- narrays, arrays, (PyObject *)dtype);
+ narrays, arrays, dtype);
if (descr == NULL) {
return NULL;
}
@@ -589,7 +589,7 @@ PyArray_ConcatenateFlattenedArrays(int narrays, PyArrayObject **arrays,
PyTypeObject *subtype = PyArray_GetSubType(narrays, arrays);
PyArray_Descr *descr = PyArray_FindConcatenationDescriptor(
- narrays, arrays, (PyObject *)dtype);
+ narrays, arrays, dtype);
if (descr == NULL) {
return NULL;
}
@@ -4614,7 +4614,7 @@ static struct PyMethodDef array_module_methods[] = {
{"set_legacy_print_mode", (PyCFunction)set_legacy_print_mode,
METH_VARARGS, NULL},
{"_discover_array_parameters", (PyCFunction)_discover_array_parameters,
- METH_VARARGS | METH_KEYWORDS, NULL},
+ METH_FASTCALL | METH_KEYWORDS, NULL},
{"_get_castingimpl", (PyCFunction)_get_castingimpl,
METH_VARARGS | METH_KEYWORDS, NULL},
{"_get_experimental_dtype_api", (PyCFunction)_get_experimental_dtype_api,
diff --git a/numpy/core/src/multiarray/nditer_constr.c b/numpy/core/src/multiarray/nditer_constr.c
index 0ed475c5b..6ae098356 100644
--- a/numpy/core/src/multiarray/nditer_constr.c
+++ b/numpy/core/src/multiarray/nditer_constr.c
@@ -1133,7 +1133,7 @@ npyiter_prepare_one_operand(PyArrayObject **op,
if (op_request_dtype != NULL) {
/* We just have a borrowed reference to op_request_dtype */
Py_SETREF(*op_dtype, PyArray_AdaptDescriptorToArray(
- *op, (PyObject *)op_request_dtype));
+ *op, NULL, op_request_dtype));
if (*op_dtype == NULL) {
return 0;
}
diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py
index fa3468179..0ba736c05 100644
--- a/numpy/core/tests/test_array_coercion.py
+++ b/numpy/core/tests/test_array_coercion.py
@@ -163,6 +163,8 @@ class TestStringDiscovery:
assert np.array(arr, dtype="S").dtype == expected
# Check that .astype() behaves identical
assert arr.astype("S").dtype == expected
+ # The DType class is accepted by `.astype()`
+ assert arr.astype(type(np.dtype("S"))).dtype == expected
@pytest.mark.parametrize("obj",
[object(), 1.2, 10**43, None, "string"],
diff --git a/numpy/core/tests/test_custom_dtypes.py b/numpy/core/tests/test_custom_dtypes.py
index 5d01fc49d..186906f5a 100644
--- a/numpy/core/tests/test_custom_dtypes.py
+++ b/numpy/core/tests/test_custom_dtypes.py
@@ -219,6 +219,16 @@ class TestSFloat:
expected = np.hypot.reduce(float_equiv, keepdims=True)
assert res.view(np.float64) * 2 == expected
+ def test_astype_class(self):
+ # Very simple test that we accept `.astype()` also on the class.
+ # ScaledFloat always returns the default descriptor, but it does
+ # check the relevant code paths.
+ arr = np.array([1., 2., 3.], dtype=object)
+
+ res = arr.astype(SF) # passing the class class
+ expected = arr.astype(SF(1.)) # above will have discovered 1. scaling
+ assert_array_equal(res.view(np.float64), expected.view(np.float64))
+
def test_type_pickle():
# can't actually unpickle, but we can pickle (if in namespace)