summaryrefslogtreecommitdiff
path: root/numpy
diff options
context:
space:
mode:
Diffstat (limited to 'numpy')
-rw-r--r--numpy/core/include/numpy/ndarraytypes.h54
-rw-r--r--numpy/core/src/multiarray/_multiarray_tests.c.src6
-rw-r--r--numpy/core/src/multiarray/abstractdtypes.c53
-rw-r--r--numpy/core/src/multiarray/array_coercion.c17
-rw-r--r--numpy/core/src/multiarray/array_method.c10
-rw-r--r--numpy/core/src/multiarray/common.c4
-rw-r--r--numpy/core/src/multiarray/common_dtype.c10
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c94
-rw-r--r--numpy/core/src/multiarray/datetime.c2
-rw-r--r--numpy/core/src/multiarray/descriptor.c7
-rw-r--r--numpy/core/src/multiarray/dtypemeta.c110
-rw-r--r--numpy/core/src/multiarray/dtypemeta.h58
-rw-r--r--numpy/core/src/multiarray/usertypes.c6
-rw-r--r--numpy/core/src/umath/_scaled_float_dtype.c40
-rw-r--r--numpy/core/src/umath/dispatching.c20
-rw-r--r--numpy/core/src/umath/legacy_array_method.c5
-rw-r--r--numpy/core/src/umath/ufunc_object.c6
-rw-r--r--numpy/core/tests/test_array_coercion.py14
-rw-r--r--numpy/core/tests/test_casting_unittests.py24
19 files changed, 321 insertions, 219 deletions
diff --git a/numpy/core/include/numpy/ndarraytypes.h b/numpy/core/include/numpy/ndarraytypes.h
index 5acd24a7e..84441e641 100644
--- a/numpy/core/include/numpy/ndarraytypes.h
+++ b/numpy/core/include/numpy/ndarraytypes.h
@@ -1879,8 +1879,6 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
typedef PyArray_Descr *(default_descr_function)(PyArray_DTypeMeta *cls);
typedef PyArray_DTypeMeta *(common_dtype_function)(
PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtyep2);
- typedef PyArray_DTypeMeta *(common_dtype_with_value_function)(
- PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtyep2, PyObject *value);
typedef PyArray_Descr *(common_instance_function)(
PyArray_Descr *dtype1, PyArray_Descr *dtyep2);
@@ -1903,55 +1901,25 @@ typedef void (PyDataMem_EventHookFunc)(void *inp, void *outp, size_t size,
* may be a pointer to the *prototype* instance?
*/
PyArray_Descr *singleton;
- /*
- * Is this DType created using the old API? This exists mainly to
- * allow for assertions in paths specific to wrapping legacy types.
- */
- npy_bool legacy;
- /* The values stored by a parametric datatype depend on its instance */
- npy_bool parametric;
- /* whether the DType can be instantiated (i.e. np.dtype cannot) */
- npy_bool abstract;
+ /* Copy of the legacy DTypes type number, usually invalid. */
+ int type_num;
- /*
- * The following fields replicate the most important dtype information.
- * In the legacy implementation most of these are stored in the
- * PyArray_Descr struct.
- */
/* The type object of the scalar instances (may be NULL?) */
PyTypeObject *scalar_type;
- /* kind for this type */
- char kind;
- /* unique-character representing this type */
- char type;
- /* flags describing data type */
- char flags;
- /* number representing this type */
- int type_num;
/*
- * Point to the original ArrFuncs.
- * NOTE: We could make a copy to detect changes to `f`.
+ * DType flags to signal legacy, parametric, or
+ * abstract. But plenty of space for additional information/flags.
*/
- PyArray_ArrFuncs *f;
+ npy_uint64 flags;
- /* DType methods, these could be moved into its own struct */
- discover_descr_from_pyobject_function *discover_descr_from_pyobject;
- is_known_scalar_type_function *is_known_scalar_type;
- default_descr_function *default_descr;
- common_dtype_function *common_dtype;
- common_dtype_with_value_function *common_dtype_with_value;
- common_instance_function *common_instance;
- /*
- * The casting implementation (ArrayMethod) to convert between two
- * instances of this DType, stored explicitly for fast access:
- */
- PyObject *within_dtype_castingimpl;
/*
- * Dictionary of ArrayMethods representing most possible casts
- * (structured and object are exceptions).
- * This should potentially become a weak mapping in the future.
+ * Use indirection in order to allow a fixed size for this struct.
+ * A stable ABI size makes creating a static DType less painful
+ * while also ensuring flexibility for all opaque API (with one
+ * indirection due the pointer lookup).
*/
- PyObject *castingimpls;
+ void *dt_slots;
+ void *reserved[3];
};
#endif /* NPY_INTERNAL_BUILD */
diff --git a/numpy/core/src/multiarray/_multiarray_tests.c.src b/numpy/core/src/multiarray/_multiarray_tests.c.src
index 4b9a4f9dd..f4764b371 100644
--- a/numpy/core/src/multiarray/_multiarray_tests.c.src
+++ b/numpy/core/src/multiarray/_multiarray_tests.c.src
@@ -14,6 +14,7 @@
#include "npy_extint128.h"
#include "array_method.h"
#include "npy_hashtable.h"
+#include "dtypemeta.h"
#if defined(MS_WIN32) || defined(__CYGWIN__)
#define EXPORT(x) __declspec(dllexport) x
@@ -1066,7 +1067,7 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
for (Py_ssize_t i = 0; i < nclass; i++) {
PyArray_DTypeMeta *from_dtype = (
(PyArray_DTypeMeta *)PySequence_Fast_GET_ITEM(classes, i));
- if (from_dtype->abstract) {
+ if (NPY_DT_is_abstract(from_dtype)) {
/*
* TODO: In principle probably needs to recursively check this,
* also we may allow casts to abstract dtypes at some point.
@@ -1077,7 +1078,8 @@ get_all_cast_information(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
PyObject *to_dtype, *cast_obj;
Py_ssize_t pos = 0;
- while (PyDict_Next(from_dtype->castingimpls, &pos, &to_dtype, &cast_obj)) {
+ while (PyDict_Next(NPY_DT_SLOTS(from_dtype)->castingimpls,
+ &pos, &to_dtype, &cast_obj)) {
if (cast_obj == Py_None) {
continue;
}
diff --git a/numpy/core/src/multiarray/abstractdtypes.c b/numpy/core/src/multiarray/abstractdtypes.c
index 587d91c49..3fa354ddc 100644
--- a/numpy/core/src/multiarray/abstractdtypes.c
+++ b/numpy/core/src/multiarray/abstractdtypes.c
@@ -150,7 +150,7 @@ initialize_and_map_pytypes_to_dtypes()
static PyArray_DTypeMeta *
int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other)
{
- if (other->legacy && other->type_num < NPY_NTYPES) {
+ if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) {
if (other->type_num == NPY_BOOL) {
/* Use the default integer for bools: */
return PyArray_DTypeFromTypeNum(NPY_LONG);
@@ -162,7 +162,7 @@ int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other)
return other;
}
}
- else if (other->legacy) {
+ else if (NPY_DT_is_legacy(other)) {
/* This is a back-compat fallback to usually do the right thing... */
return PyArray_DTypeFromTypeNum(NPY_UINT8);
}
@@ -174,7 +174,7 @@ int_common_dtype(PyArray_DTypeMeta *NPY_UNUSED(cls), PyArray_DTypeMeta *other)
static PyArray_DTypeMeta *
float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
- if (other->legacy && other->type_num < NPY_NTYPES) {
+ if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) {
if (other->type_num == NPY_BOOL || PyTypeNum_ISINTEGER(other->type_num)) {
/* Use the default integer for bools and ints: */
return PyArray_DTypeFromTypeNum(NPY_DOUBLE);
@@ -189,7 +189,7 @@ float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
Py_INCREF(cls);
return cls;
}
- else if (other->legacy) {
+ else if (NPY_DT_is_legacy(other)) {
/* This is a back-compat fallback to usually do the right thing... */
return PyArray_DTypeFromTypeNum(NPY_HALF);
}
@@ -201,7 +201,7 @@ float_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
static PyArray_DTypeMeta *
complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
- if (other->legacy && other->type_num < NPY_NTYPES) {
+ if (NPY_DT_is_legacy(other) && other->type_num < NPY_NTYPES) {
if (other->type_num == NPY_BOOL ||
PyTypeNum_ISINTEGER(other->type_num)) {
/* Use the default integer for bools and ints: */
@@ -227,7 +227,7 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
return other;
}
}
- else if (other->legacy) {
+ else if (NPY_DT_is_legacy(other)) {
/* This is a back-compat fallback to usually do the right thing... */
return PyArray_DTypeFromTypeNum(NPY_CFLOAT);
}
@@ -246,17 +246,27 @@ complex_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
* `Floating`, `Complex`, and `Integer` (both signed and unsigned).
* They will have to be renamed and exposed in that capacity.
*/
+NPY_DType_Slots pyintabstractdtype_slots = {
+ .default_descr = int_default_descriptor,
+ .discover_descr_from_pyobject = discover_descriptor_from_pyint,
+ .common_dtype = int_common_dtype,
+};
+
NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyIntAbstractDType = {{{
PyVarObject_HEAD_INIT(&PyArrayDTypeMeta_Type, 0)
.tp_basicsize = sizeof(PyArray_Descr),
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_name = "numpy._IntegerAbstractDType",
},},
- .abstract = 1,
- .default_descr = int_default_descriptor,
- .discover_descr_from_pyobject = discover_descriptor_from_pyint,
- .common_dtype = int_common_dtype,
- .kind = 'i',
+ .flags = NPY_DT_ABSTRACT,
+ .dt_slots = &pyintabstractdtype_slots,
+};
+
+
+NPY_DType_Slots pyfloatabstractdtype_slots = {
+ .default_descr = float_default_descriptor,
+ .discover_descr_from_pyobject = discover_descriptor_from_pyfloat,
+ .common_dtype = float_common_dtype,
};
NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{
@@ -265,11 +275,15 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyFloatAbstractDType = {{{
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_name = "numpy._FloatAbstractDType",
},},
- .abstract = 1,
- .default_descr = float_default_descriptor,
- .discover_descr_from_pyobject = discover_descriptor_from_pyfloat,
- .common_dtype = float_common_dtype,
- .kind = 'f',
+ .flags = NPY_DT_ABSTRACT,
+ .dt_slots = &pyfloatabstractdtype_slots,
+};
+
+
+NPY_DType_Slots pycomplexabstractdtype_slots = {
+ .default_descr = complex_default_descriptor,
+ .discover_descr_from_pyobject = discover_descriptor_from_pycomplex,
+ .common_dtype = complex_common_dtype,
};
NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{
@@ -278,9 +292,6 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArray_PyComplexAbstractDType = {{{
.tp_flags = Py_TPFLAGS_DEFAULT,
.tp_name = "numpy._ComplexAbstractDType",
},},
- .abstract = 1,
- .default_descr = complex_default_descriptor,
- .discover_descr_from_pyobject = discover_descriptor_from_pycomplex,
- .common_dtype = complex_common_dtype,
- .kind = 'c',
+ .flags = NPY_DT_ABSTRACT,
+ .dt_slots = &pycomplexabstractdtype_slots,
};
diff --git a/numpy/core/src/multiarray/array_coercion.c b/numpy/core/src/multiarray/array_coercion.c
index 22050a56f..89719f129 100644
--- a/numpy/core/src/multiarray/array_coercion.c
+++ b/numpy/core/src/multiarray/array_coercion.c
@@ -154,7 +154,7 @@ _PyArray_MapPyTypeToDType(
* We expect that user dtypes (for now) will subclass some numpy
* scalar class to allow automatic discovery.
*/
- if (DType->legacy) {
+ if (NPY_DT_is_legacy(DType)) {
/*
* For legacy user dtypes, discovery relied on subclassing, but
* arbitrary type objects are supported, so do nothing.
@@ -257,8 +257,7 @@ discover_dtype_from_pyobject(
* asked to attempt to do so later, if no other matching DType exists.)
*/
if ((Py_TYPE(obj) == fixed_DType->scalar_type) ||
- (fixed_DType->is_known_scalar_type != NULL &&
- fixed_DType->is_known_scalar_type(fixed_DType, Py_TYPE(obj)))) {
+ NPY_DT_CALL_is_known_scalar_type(fixed_DType, Py_TYPE(obj))) {
Py_INCREF(fixed_DType);
return fixed_DType;
}
@@ -346,10 +345,10 @@ find_scalar_descriptor(
* chance. This allows for example string, to call `str(obj)` to
* figure out the length for arbitrary objects.
*/
- descr = fixed_DType->discover_descr_from_pyobject(fixed_DType, obj);
+ descr = NPY_DT_CALL_discover_descr_from_pyobject(fixed_DType, obj);
}
else {
- descr = DType->discover_descr_from_pyobject(DType, obj);
+ descr = NPY_DT_CALL_discover_descr_from_pyobject(DType, obj);
}
if (descr == NULL) {
return NULL;
@@ -425,7 +424,7 @@ PyArray_Pack(PyArray_Descr *descr, char *item, PyObject *value)
return descr->f->setitem(value, item, &arr_fields);
}
PyArray_Descr *tmp_descr;
- tmp_descr = DType->discover_descr_from_pyobject(DType, value);
+ tmp_descr = NPY_DT_CALL_discover_descr_from_pyobject(DType, value);
Py_DECREF(DType);
if (tmp_descr == NULL) {
return -1;
@@ -713,7 +712,7 @@ find_descriptor_from_array(
return 0;
}
- if (NPY_UNLIKELY(DType->parametric && PyArray_ISOBJECT(arr))) {
+ if (NPY_UNLIKELY(NPY_DT_is_parametric(DType) && PyArray_ISOBJECT(arr))) {
/*
* We have one special case, if (and only if) the input array is of
* object DType and the dtype is not fixed already but parametric.
@@ -833,7 +832,7 @@ PyArray_AdaptDescriptorToArray(PyArrayObject *arr, PyObject *dtype)
}
if (new_dtype == NULL) {
/* This is an object array but contained no elements, use default */
- new_dtype = new_DType->default_descr(new_DType);
+ new_dtype = NPY_DT_CALL_default_descr(new_DType);
}
}
Py_DECREF(new_DType);
@@ -1376,7 +1375,7 @@ PyArray_DiscoverDTypeAndShape(
* the correct default.
*/
if (fixed_DType != NULL) {
- *out_descr = fixed_DType->default_descr(fixed_DType);
+ *out_descr = NPY_DT_CALL_default_descr(fixed_DType);
if (*out_descr == NULL) {
goto fail;
}
diff --git a/numpy/core/src/multiarray/array_method.c b/numpy/core/src/multiarray/array_method.c
index 8185650e7..44ba8c733 100644
--- a/numpy/core/src/multiarray/array_method.c
+++ b/numpy/core/src/multiarray/array_method.c
@@ -72,7 +72,7 @@ default_resolve_descriptors(
output_descrs[i] = ensure_dtype_nbo(input_descrs[i]);
}
else {
- output_descrs[i] = dtype->default_descr(dtype);
+ output_descrs[i] = NPY_DT_CALL_default_descr(dtype);
}
if (NPY_UNLIKELY(output_descrs[i] == NULL)) {
goto fail;
@@ -106,7 +106,7 @@ default_resolve_descriptors(
output_descrs[i] = ensure_dtype_nbo(input_descrs[i]);
}
else {
- output_descrs[i] = common_dtype->default_descr(common_dtype);
+ output_descrs[i] = NPY_DT_CALL_default_descr(common_dtype);
}
if (NPY_UNLIKELY(output_descrs[i] == NULL)) {
goto fail;
@@ -232,7 +232,7 @@ validate_spec(PyArrayMethod_Spec *spec)
"(method: %s)", spec->dtypes[i], spec->name);
return -1;
}
- if (spec->dtypes[i]->abstract && i < spec->nin) {
+ if (NPY_DT_is_abstract(spec->dtypes[i]) && i < spec->nin) {
PyErr_Format(PyExc_TypeError,
"abstract DType %S are currently not allowed for inputs."
"(method: %s defined at %s)", spec->dtypes[i], spec->name);
@@ -328,7 +328,7 @@ fill_arraymethod_from_slots(
return -1;
}
}
- if (i >= meth->nin && res->dtypes[i]->parametric) {
+ if (i >= meth->nin && NPY_DT_is_parametric(res->dtypes[i])) {
PyErr_Format(PyExc_TypeError,
"must provide a `resolve_descriptors` function if any "
"output DType is parametric. (method: %s)",
@@ -585,7 +585,7 @@ boundarraymethod__resolve_descripors(
*/
int parametric = 0;
for (int i = 0; i < nin + nout; i++) {
- if (self->dtypes[i]->parametric) {
+ if (NPY_DT_is_parametric(self->dtypes[i])) {
parametric = 1;
break;
}
diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c
index 841ed799d..1fd9ab1a3 100644
--- a/numpy/core/src/multiarray/common.c
+++ b/numpy/core/src/multiarray/common.c
@@ -46,8 +46,8 @@ _array_find_python_scalar_type(PyObject *op)
return PyArray_DescrFromType(NPY_CDOUBLE);
}
else if (PyLong_Check(op)) {
- return PyArray_PyIntAbstractDType.discover_descr_from_pyobject(
- &PyArray_PyIntAbstractDType, op);
+ return NPY_DT_CALL_discover_descr_from_pyobject(
+ &PyArray_PyIntAbstractDType, op);
}
return NULL;
}
diff --git a/numpy/core/src/multiarray/common_dtype.c b/numpy/core/src/multiarray/common_dtype.c
index a88085f6f..659580c98 100644
--- a/numpy/core/src/multiarray/common_dtype.c
+++ b/numpy/core/src/multiarray/common_dtype.c
@@ -50,10 +50,10 @@ PyArray_CommonDType(PyArray_DTypeMeta *dtype1, PyArray_DTypeMeta *dtype2)
PyArray_DTypeMeta *common_dtype;
- common_dtype = dtype1->common_dtype(dtype1, dtype2);
+ common_dtype = NPY_DT_CALL_common_dtype(dtype1, dtype2);
if (common_dtype == (PyArray_DTypeMeta *)Py_NotImplemented) {
Py_DECREF(common_dtype);
- common_dtype = dtype2->common_dtype(dtype2, dtype1);
+ common_dtype = NPY_DT_CALL_common_dtype(dtype2, dtype1);
}
if (common_dtype == NULL) {
return NULL;
@@ -128,7 +128,7 @@ reduce_dtypes_to_most_knowledgeable(
Py_XSETREF(res, dtypes[low]);
}
else {
- if (dtypes[high]->abstract) {
+ if (NPY_DT_is_abstract(dtypes[high])) {
/*
* Priority inversion, start with abstract, because if it
* returns `other`, we can let other pass instead.
@@ -138,7 +138,7 @@ reduce_dtypes_to_most_knowledgeable(
dtypes[high] = tmp;
}
- Py_XSETREF(res, dtypes[low]->common_dtype(dtypes[low], dtypes[high]));
+ Py_XSETREF(res, NPY_DT_CALL_common_dtype(dtypes[low], dtypes[high]));
if (res == NULL) {
return NULL;
}
@@ -270,7 +270,7 @@ PyArray_PromoteDTypeSequence(
* a higher category). We assume that the result is not in a lower
* category.
*/
- PyArray_DTypeMeta *promotion = main_dtype->common_dtype(
+ PyArray_DTypeMeta *promotion = NPY_DT_CALL_common_dtype(
main_dtype, dtypes[i]);
if (promotion == NULL) {
Py_XSETREF(result, NULL);
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 067c356ca..e3b25d076 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -70,10 +70,10 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
{
PyObject *res;
if (from == to) {
- res = from->within_dtype_castingimpl;
+ res = NPY_DT_SLOTS(from)->within_dtype_castingimpl;
}
else {
- res = PyDict_GetItemWithError(from->castingimpls, (PyObject *)to);
+ res = PyDict_GetItemWithError(NPY_DT_SLOTS(from)->castingimpls, (PyObject *)to);
}
if (res != NULL || PyErr_Occurred()) {
Py_XINCREF(res);
@@ -105,11 +105,11 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
return NULL;
}
else {
- if (from->parametric || to->parametric) {
+ if (NPY_DT_is_parametric(from) || NPY_DT_is_parametric(to)) {
Py_RETURN_NONE;
}
/* Reject non-legacy dtypes (they need to use the new API) */
- if (!from->legacy || !to->legacy) {
+ if (!NPY_DT_is_legacy(from) || !NPY_DT_is_legacy(to)) {
Py_RETURN_NONE;
}
if (from != to) {
@@ -119,7 +119,8 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
if (castfunc == NULL) {
PyErr_Clear();
/* Remember that this cast is not possible */
- if (PyDict_SetItem(from->castingimpls, (PyObject *) to, Py_None) < 0) {
+ if (PyDict_SetItem(NPY_DT_SLOTS(from)->castingimpls,
+ (PyObject *) to, Py_None) < 0) {
return NULL;
}
Py_RETURN_NONE;
@@ -147,7 +148,8 @@ PyArray_GetCastingImpl(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
Py_DECREF(res);
return NULL;
}
- if (PyDict_SetItem(from->castingimpls, (PyObject *)to, res) < 0) {
+ if (PyDict_SetItem(NPY_DT_SLOTS(from)->castingimpls,
+ (PyObject *)to, res) < 0) {
Py_DECREF(res);
return NULL;
}
@@ -946,12 +948,12 @@ PyArray_CastDescrToDType(PyArray_Descr *descr, PyArray_DTypeMeta *given_DType)
Py_INCREF(descr);
return descr;
}
- if (!given_DType->parametric) {
+ if (!NPY_DT_is_parametric(given_DType)) {
/*
* Don't actually do anything, the default is always the result
* of any cast.
*/
- return given_DType->default_descr(given_DType);
+ return NPY_DT_CALL_default_descr(given_DType);
}
if (PyObject_TypeCheck((PyObject *)descr, (PyTypeObject *)given_DType)) {
Py_INCREF(descr);
@@ -1040,7 +1042,7 @@ PyArray_FindConcatenationDescriptor(
Py_SETREF(result, NULL);
goto finish;
}
- Py_SETREF(result, common_dtype->common_instance(result, curr));
+ Py_SETREF(result, NPY_DT_SLOTS(common_dtype)->common_instance(result, curr));
Py_DECREF(curr);
if (result == NULL) {
goto finish;
@@ -1074,9 +1076,9 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2)
return NULL;
}
- if (!common_dtype->parametric) {
+ if (!NPY_DT_is_parametric(common_dtype)) {
/* Note that this path loses all metadata */
- res = common_dtype->default_descr(common_dtype);
+ res = NPY_DT_CALL_default_descr(common_dtype);
Py_DECREF(common_dtype);
return res;
}
@@ -1098,7 +1100,7 @@ PyArray_PromoteTypes(PyArray_Descr *type1, PyArray_Descr *type2)
* And find the common instance of the two inputs
* NOTE: Common instance preserves metadata (normally and of one input)
*/
- res = common_dtype->common_instance(type1, type2);
+ res = NPY_DT_SLOTS(common_dtype)->common_instance(type1, type2);
Py_DECREF(type1);
Py_DECREF(type2);
Py_DECREF(common_dtype);
@@ -1503,7 +1505,7 @@ should_use_min_scalar(npy_intp narrs, PyArrayObject **arr,
/* Compute the maximum "kinds" and whether everything is scalar */
for (npy_intp i = 0; i < narrs; ++i) {
- if (!NPY_DTYPE(PyArray_DESCR(arr[i]))->legacy) {
+ if (!NPY_DT_is_legacy(NPY_DTYPE(PyArray_DESCR(arr[i])))) {
return 0;
}
if (PyArray_NDIM(arr[i]) == 0) {
@@ -1527,7 +1529,7 @@ should_use_min_scalar(npy_intp narrs, PyArrayObject **arr,
* finish computing the max array kind
*/
for (npy_intp i = 0; i < ndtypes; ++i) {
- if (!NPY_DTYPE(dtypes[i])->legacy) {
+ if (!NPY_DT_is_legacy(NPY_DTYPE(dtypes[i]))) {
return 0;
}
int kind = dtype_kind_to_simplified_ordering(dtypes[i]->kind);
@@ -1661,9 +1663,9 @@ PyArray_ResultType(
goto error;
}
- if (common_dtype->abstract) {
+ if (NPY_DT_is_abstract(common_dtype)) {
/* (ab)use default descriptor to define a default */
- PyArray_Descr *tmp_descr = common_dtype->default_descr(common_dtype);
+ PyArray_Descr *tmp_descr = NPY_DT_CALL_default_descr(common_dtype);
if (tmp_descr == NULL) {
goto error;
}
@@ -1676,9 +1678,9 @@ PyArray_ResultType(
* NOTE: Code duplicates `PyArray_CastToDTypeAndPromoteDescriptors`, but
* supports special handling of the abstract values.
*/
- if (!common_dtype->parametric) {
+ if (!NPY_DT_is_parametric(common_dtype)) {
/* Note that this "fast" path loses all metadata */
- result = common_dtype->default_descr(common_dtype);
+ result = NPY_DT_CALL_default_descr(common_dtype);
}
else {
result = PyArray_CastDescrToDType(all_descriptors[0], common_dtype);
@@ -1700,13 +1702,13 @@ PyArray_ResultType(
if (tmp == NULL) {
goto error;
}
- curr = common_dtype->discover_descr_from_pyobject(common_dtype, tmp);
+ curr = NPY_DT_CALL_discover_descr_from_pyobject(common_dtype, tmp);
Py_DECREF(tmp);
}
if (curr == NULL) {
goto error;
}
- Py_SETREF(result, common_dtype->common_instance(result, curr));
+ Py_SETREF(result, NPY_DT_SLOTS(common_dtype)->common_instance(result, curr));
Py_DECREF(curr);
if (result == NULL) {
goto error;
@@ -1884,10 +1886,10 @@ PyArray_CastToDTypeAndPromoteDescriptors(
if (result == NULL || ndescr == 1) {
return result;
}
- if (!DType->parametric) {
+ if (!NPY_DT_is_parametric(DType)) {
/* Note that this "fast" path loses all metadata */
Py_DECREF(result);
- return DType->default_descr(DType);
+ return NPY_DT_CALL_default_descr(DType);
}
for (npy_intp i = 1; i < ndescr; i++) {
@@ -1896,7 +1898,7 @@ PyArray_CastToDTypeAndPromoteDescriptors(
Py_DECREF(result);
return NULL;
}
- Py_SETREF(result, DType->common_instance(result, curr));
+ Py_SETREF(result, NPY_DT_SLOTS(DType)->common_instance(result, curr));
Py_DECREF(curr);
if (result == NULL) {
return NULL;
@@ -2066,7 +2068,7 @@ PyArray_ObjectType(PyObject *op, int minimum_type)
if (dtype == NULL) {
ret = NPY_DEFAULT_TYPE;
}
- else if (!NPY_DTYPE(dtype)->legacy) {
+ else if (!NPY_DT_is_legacy(NPY_DTYPE(dtype))) {
/*
* TODO: If we keep all type number style API working, by defining
* type numbers always. We may be able to allow this again.
@@ -2210,25 +2212,26 @@ PyArray_AddCastingImplementation(PyBoundArrayMethodObject *meth)
meth->method->name);
return -1;
}
- if (meth->dtypes[0]->within_dtype_castingimpl != NULL) {
+ if (NPY_DT_SLOTS(meth->dtypes[0])->within_dtype_castingimpl != NULL) {
PyErr_Format(PyExc_RuntimeError,
"A cast was already added for %S -> %S. (method: %s)",
meth->dtypes[0], meth->dtypes[1], meth->method->name);
return -1;
}
Py_INCREF(meth->method);
- meth->dtypes[0]->within_dtype_castingimpl = (PyObject *)meth->method;
+ NPY_DT_SLOTS(meth->dtypes[0])->within_dtype_castingimpl = (
+ (PyObject *)meth->method);
return 0;
}
- if (PyDict_Contains(meth->dtypes[0]->castingimpls,
+ if (PyDict_Contains(NPY_DT_SLOTS(meth->dtypes[0])->castingimpls,
(PyObject *)meth->dtypes[1])) {
PyErr_Format(PyExc_RuntimeError,
"A cast was already added for %S -> %S. (method: %s)",
meth->dtypes[0], meth->dtypes[1], meth->method->name);
return -1;
}
- if (PyDict_SetItem(meth->dtypes[0]->castingimpls,
+ if (PyDict_SetItem(NPY_DT_SLOTS(meth->dtypes[0])->castingimpls,
(PyObject *)meth->dtypes[1], (PyObject *)meth->method) < 0) {
return -1;
}
@@ -2331,7 +2334,7 @@ simple_cast_resolve_descriptors(
PyArray_Descr *given_descrs[2],
PyArray_Descr *loop_descrs[2])
{
- assert(dtypes[0]->legacy && dtypes[1]->legacy);
+ assert(NPY_DT_is_legacy(dtypes[0]) && NPY_DT_is_legacy(dtypes[1]));
loop_descrs[0] = ensure_dtype_nbo(given_descrs[0]);
if (loop_descrs[0] == NULL) {
@@ -2345,7 +2348,7 @@ simple_cast_resolve_descriptors(
}
}
else {
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
}
if (self->casting != NPY_NO_CASTING) {
@@ -2480,7 +2483,8 @@ add_numeric_cast(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
assert(slots[1].pfunc && slots[2].pfunc && slots[3].pfunc && slots[4].pfunc);
/* Find the correct casting level, and special case no-cast */
- if (dtypes[0]->kind == dtypes[1]->kind && from_itemsize == to_itemsize) {
+ if (dtypes[0]->singleton->kind == dtypes[1]->singleton->kind
+ && from_itemsize == to_itemsize) {
spec.casting = NPY_EQUIV_CASTING;
/* When there is no casting (equivalent C-types) use byteswap loops */
@@ -2497,8 +2501,8 @@ add_numeric_cast(PyArray_DTypeMeta *from, PyArray_DTypeMeta *to)
else if (_npy_can_cast_safely_table[from->type_num][to->type_num]) {
spec.casting = NPY_SAFE_CASTING;
}
- else if (dtype_kind_to_ordering(dtypes[0]->kind) <=
- dtype_kind_to_ordering(dtypes[1]->kind)) {
+ else if (dtype_kind_to_ordering(dtypes[0]->singleton->kind) <=
+ dtype_kind_to_ordering(dtypes[1]->singleton->kind)) {
spec.casting = NPY_SAME_KIND_CASTING;
}
else {
@@ -2556,7 +2560,7 @@ cast_to_string_resolve_descriptors(
* a multiple of eight.
*/
npy_intp size = -1;
- switch (dtypes[0]->type_num) {
+ switch (given_descrs[0]->type_num) {
case NPY_BOOL:
case NPY_UBYTE:
case NPY_BYTE:
@@ -2568,18 +2572,18 @@ cast_to_string_resolve_descriptors(
case NPY_LONG:
case NPY_ULONGLONG:
case NPY_LONGLONG:
- assert(dtypes[0]->singleton->elsize <= 8);
- assert(dtypes[0]->singleton->elsize > 0);
- if (dtypes[0]->kind == 'b') {
+ assert(given_descrs[0]->elsize <= 8);
+ assert(given_descrs[0]->elsize > 0);
+ if (given_descrs[0]->kind == 'b') {
/* 5 chars needed for cast to 'True' or 'False' */
size = 5;
}
- else if (dtypes[0]->kind == 'u') {
- size = REQUIRED_STR_LEN[dtypes[0]->singleton->elsize];
+ else if (given_descrs[0]->kind == 'u') {
+ size = REQUIRED_STR_LEN[given_descrs[0]->elsize];
}
- else if (dtypes[0]->kind == 'i') {
+ else if (given_descrs[0]->kind == 'i') {
/* Add character for sign symbol */
- size = REQUIRED_STR_LEN[dtypes[0]->singleton->elsize] + 1;
+ size = REQUIRED_STR_LEN[given_descrs[0]->elsize] + 1;
}
break;
case NPY_HALF:
@@ -3067,7 +3071,7 @@ structured_to_nonstructured_resolve_descriptors(
/* Void dtypes always do the full cast. */
if (given_descrs[1] == NULL) {
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
/*
* Special case strings here, it should be useless (and only actually
* work for empty arrays). Possibly this should simply raise for
@@ -3426,7 +3430,7 @@ object_to_any_resolve_descriptors(
* here is that e.g. "M8" input is considered to be the DType class,
* and by allowing it here, we go back to the "M8" instance.
*/
- if (dtypes[1]->parametric) {
+ if (NPY_DT_is_parametric(dtypes[1])) {
PyErr_Format(PyExc_TypeError,
"casting from object to the parametric DType %S requires "
"the specified output dtype instance. "
@@ -3434,7 +3438,7 @@ object_to_any_resolve_descriptors(
"should be discovered automatically, however.", dtypes[1]);
return -1;
}
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
if (loop_descrs[1] == NULL) {
return -1;
}
@@ -3490,7 +3494,7 @@ any_to_object_resolve_descriptors(
PyArray_Descr *loop_descrs[2])
{
if (given_descrs[1] == NULL) {
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
if (loop_descrs[1] == NULL) {
return -1;
}
diff --git a/numpy/core/src/multiarray/datetime.c b/numpy/core/src/multiarray/datetime.c
index b9d81e836..182eb12f9 100644
--- a/numpy/core/src/multiarray/datetime.c
+++ b/numpy/core/src/multiarray/datetime.c
@@ -3996,7 +3996,7 @@ string_to_datetime_cast_resolve_descriptors(
{
if (given_descrs[1] == NULL) {
/* NOTE: This doesn't actually work, and will error during the cast */
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
if (loop_descrs[1] == NULL) {
return -1;
}
diff --git a/numpy/core/src/multiarray/descriptor.c b/numpy/core/src/multiarray/descriptor.c
index 58aa608c3..50964dab8 100644
--- a/numpy/core/src/multiarray/descriptor.c
+++ b/numpy/core/src/multiarray/descriptor.c
@@ -20,6 +20,7 @@
#include "alloc.h"
#include "assert.h"
#include "npy_buffer.h"
+#include "dtypemeta.h"
/*
* offset: A starting offset.
@@ -3544,9 +3545,7 @@ NPY_NO_EXPORT PyArray_DTypeMeta PyArrayDescr_TypeFull = {
.tp_new = arraydescr_new,
},},
.type_num = -1,
- .kind = '\0',
- .abstract = 1,
- .parametric = 0,
- .singleton = 0,
+ .flags = NPY_DT_ABSTRACT,
+ .singleton = NULL,
.scalar_type = NULL,
};
diff --git a/numpy/core/src/multiarray/dtypemeta.c b/numpy/core/src/multiarray/dtypemeta.c
index 4ee721964..597468c50 100644
--- a/numpy/core/src/multiarray/dtypemeta.c
+++ b/numpy/core/src/multiarray/dtypemeta.c
@@ -27,7 +27,8 @@ dtypemeta_dealloc(PyArray_DTypeMeta *self) {
Py_XDECREF(self->scalar_type);
Py_XDECREF(self->singleton);
- Py_XDECREF(self->castingimpls);
+ Py_XDECREF(NPY_DT_SLOTS(self)->castingimpls);
+ PyMem_Free(self->dt_slots);
PyType_Type.tp_dealloc((PyObject *) self);
}
@@ -89,7 +90,7 @@ dtypemeta_traverse(PyArray_DTypeMeta *type, visitproc visit, void *arg)
* defined types). It should be revised at that time.
*/
assert(0);
- assert(!type->legacy && (PyTypeObject *)type != &PyArrayDescr_Type);
+ assert(!NPY_DT_is_legacy(type) && (PyTypeObject *)type != &PyArrayDescr_Type);
Py_VISIT(type->singleton);
Py_VISIT(type->scalar_type);
return PyType_Type.tp_traverse((PyObject *)type, visit, arg);
@@ -101,7 +102,7 @@ legacy_dtype_default_new(PyArray_DTypeMeta *self,
PyObject *args, PyObject *kwargs)
{
/* TODO: This should allow endianess and possibly metadata */
- if (self->parametric) {
+ if (NPY_DT_is_parametric(self)) {
/* reject parametric ones since we would need to get unit, etc. info */
PyErr_Format(PyExc_TypeError,
"Preliminary-API: Flexible/Parametric legacy DType '%S' can "
@@ -126,7 +127,7 @@ nonparametric_discover_descr_from_pyobject(
PyArray_DTypeMeta *cls, PyObject *obj)
{
/* If the object is of the correct scalar type return our singleton */
- assert(!cls->parametric);
+ assert(!NPY_DT_is_parametric(cls));
Py_INCREF(cls->singleton);
return cls->singleton;
}
@@ -382,7 +383,7 @@ static PyArray_DTypeMeta *
default_builtin_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
assert(cls->type_num < NPY_NTYPES);
- if (!other->legacy || other->type_num > cls->type_num) {
+ if (!NPY_DT_is_legacy(other) || other->type_num > cls->type_num) {
/*
* Let the more generic (larger type number) DType handle this
* (note that half is after all others, which works out here.)
@@ -409,7 +410,7 @@ static PyArray_DTypeMeta *
string_unicode_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
assert(cls->type_num < NPY_NTYPES && cls != other);
- if (!other->legacy || (!PyTypeNum_ISNUMBER(other->type_num) &&
+ if (!NPY_DT_is_legacy(other) || (!PyTypeNum_ISNUMBER(other->type_num) &&
/* Not numeric so defer unless cls is unicode and other is string */
!(cls->type_num == NPY_UNICODE && other->type_num == NPY_STRING))) {
Py_INCREF(Py_NotImplemented);
@@ -536,7 +537,7 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
}
Py_ssize_t name_length = strlen(scalar_name) + 14;
- char *tp_name = malloc(name_length);
+ char *tp_name = PyMem_Malloc(name_length);
if (tp_name == NULL) {
PyErr_NoMemory();
return -1;
@@ -544,11 +545,20 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
snprintf(tp_name, name_length, "numpy.dtype[%s]", scalar_name);
- PyArray_DTypeMeta *dtype_class = malloc(sizeof(PyArray_DTypeMeta));
+ NPY_DType_Slots *dt_slots = PyMem_Malloc(sizeof(NPY_DType_Slots));
+ if (dt_slots == NULL) {
+ PyMem_Free(tp_name);
+ return -1;
+ }
+ memset(dt_slots, '\0', sizeof(NPY_DType_Slots));
+
+ PyArray_DTypeMeta *dtype_class = PyMem_Malloc(sizeof(PyArray_DTypeMeta));
if (dtype_class == NULL) {
- PyDataMem_FREE(tp_name);
+ PyMem_Free(tp_name);
+ PyMem_Free(dt_slots);
return -1;
}
+
/*
* Initialize the struct fields identically to static code by copying
* a prototype instances for everything except our own fields which
@@ -567,21 +577,21 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
.tp_base = &PyArrayDescr_Type,
.tp_new = (newfunc)legacy_dtype_default_new,
},},
- .legacy = 1,
- .abstract = 0, /* this is a concrete DType */
+ .flags = NPY_DT_LEGACY,
/* Further fields are not common between DTypes */
};
memcpy(dtype_class, &prototype, sizeof(PyArray_DTypeMeta));
/* Fix name of the Type*/
((PyTypeObject *)dtype_class)->tp_name = tp_name;
+ dtype_class->dt_slots = dt_slots;
/* Let python finish the initialization (probably unnecessary) */
if (PyType_Ready((PyTypeObject *)dtype_class) < 0) {
Py_DECREF(dtype_class);
return -1;
}
- dtype_class->castingimpls = PyDict_New();
- if (dtype_class->castingimpls == NULL) {
+ dt_slots->castingimpls = PyDict_New();
+ if (dt_slots->castingimpls == NULL) {
Py_DECREF(dtype_class);
return -1;
}
@@ -594,56 +604,54 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
Py_INCREF(descr->typeobj);
dtype_class->scalar_type = descr->typeobj;
dtype_class->type_num = descr->type_num;
- dtype_class->type = descr->type;
- dtype_class->f = descr->f;
- dtype_class->kind = descr->kind;
+ dt_slots->f = *(descr->f);
/* Set default functions (correct for most dtypes, override below) */
- dtype_class->default_descr = nonparametric_default_descr;
- dtype_class->discover_descr_from_pyobject = (
+ dt_slots->default_descr = nonparametric_default_descr;
+ dt_slots->discover_descr_from_pyobject = (
nonparametric_discover_descr_from_pyobject);
- dtype_class->is_known_scalar_type = python_builtins_are_known_scalar_types;
- dtype_class->common_dtype = default_builtin_common_dtype;
- dtype_class->common_instance = NULL;
+ dt_slots->is_known_scalar_type = python_builtins_are_known_scalar_types;
+ dt_slots->common_dtype = default_builtin_common_dtype;
+ dt_slots->common_instance = NULL;
if (PyTypeNum_ISSIGNED(dtype_class->type_num)) {
/* Convert our scalars (raise on too large unsigned and NaN, etc.) */
- dtype_class->is_known_scalar_type = signed_integers_is_known_scalar_types;
+ dt_slots->is_known_scalar_type = signed_integers_is_known_scalar_types;
}
if (PyTypeNum_ISUSERDEF(descr->type_num)) {
- dtype_class->common_dtype = legacy_userdtype_common_dtype_function;
+ dt_slots->common_dtype = legacy_userdtype_common_dtype_function;
}
else if (descr->type_num == NPY_OBJECT) {
- dtype_class->common_dtype = object_common_dtype;
+ dt_slots->common_dtype = object_common_dtype;
}
else if (PyTypeNum_ISDATETIME(descr->type_num)) {
/* Datetimes are flexible, but were not considered previously */
- dtype_class->parametric = NPY_TRUE;
- dtype_class->default_descr = datetime_and_timedelta_default_descr;
- dtype_class->discover_descr_from_pyobject = (
+ dtype_class->flags |= NPY_DT_PARAMETRIC;
+ dt_slots->default_descr = datetime_and_timedelta_default_descr;
+ dt_slots->discover_descr_from_pyobject = (
discover_datetime_and_timedelta_from_pyobject);
- dtype_class->common_dtype = datetime_common_dtype;
- dtype_class->common_instance = datetime_type_promotion;
+ dt_slots->common_dtype = datetime_common_dtype;
+ dt_slots->common_instance = datetime_type_promotion;
if (descr->type_num == NPY_DATETIME) {
- dtype_class->is_known_scalar_type = datetime_known_scalar_types;
+ dt_slots->is_known_scalar_type = datetime_known_scalar_types;
}
}
else if (PyTypeNum_ISFLEXIBLE(descr->type_num)) {
- dtype_class->parametric = NPY_TRUE;
+ dtype_class->flags |= NPY_DT_PARAMETRIC;
if (descr->type_num == NPY_VOID) {
- dtype_class->default_descr = void_default_descr;
- dtype_class->discover_descr_from_pyobject = (
+ dt_slots->default_descr = void_default_descr;
+ dt_slots->discover_descr_from_pyobject = (
void_discover_descr_from_pyobject);
- dtype_class->common_instance = void_common_instance;
+ dt_slots->common_instance = void_common_instance;
}
else {
- dtype_class->default_descr = string_and_unicode_default_descr;
- dtype_class->is_known_scalar_type = string_known_scalar_types;
- dtype_class->discover_descr_from_pyobject = (
+ dt_slots->default_descr = string_and_unicode_default_descr;
+ dt_slots->is_known_scalar_type = string_known_scalar_types;
+ dt_slots->discover_descr_from_pyobject = (
string_discover_descr_from_pyobject);
- dtype_class->common_dtype = string_unicode_common_dtype;
- dtype_class->common_instance = string_unicode_common_instance;
+ dt_slots->common_dtype = string_unicode_common_dtype;
+ dt_slots->common_instance = string_unicode_common_instance;
}
}
@@ -660,17 +668,28 @@ dtypemeta_wrap_legacy_descriptor(PyArray_Descr *descr)
}
+static PyObject *
+dtypemeta_get_abstract(PyArray_DTypeMeta *self) {
+ return PyBool_FromLong(NPY_DT_is_abstract(self));
+}
+
+static PyObject *
+dtypemeta_get_parametric(PyArray_DTypeMeta *self) {
+ return PyBool_FromLong(NPY_DT_is_parametric(self));
+}
+
/*
- * Simple exposed information, defined for each DType (class). This is
- * preliminary (the flags should also return bools).
+ * Simple exposed information, defined for each DType (class).
*/
+static PyGetSetDef dtypemeta_getset[] = {
+ {"_abstract", (getter)dtypemeta_get_abstract, NULL, NULL, NULL},
+ {"_parametric", (getter)dtypemeta_get_parametric, NULL, NULL, NULL},
+ {NULL, NULL, NULL, NULL, NULL}
+};
+
static PyMemberDef dtypemeta_members[] = {
- {"_abstract",
- T_BYTE, offsetof(PyArray_DTypeMeta, abstract), READONLY, NULL},
{"type",
T_OBJECT, offsetof(PyArray_DTypeMeta, scalar_type), READONLY, NULL},
- {"_parametric",
- T_BYTE, offsetof(PyArray_DTypeMeta, parametric), READONLY, NULL},
{NULL, 0, 0, 0, NULL},
};
@@ -683,6 +702,7 @@ NPY_NO_EXPORT PyTypeObject PyArrayDTypeMeta_Type = {
/* Types are garbage collected (see dtypemeta_is_gc documentation) */
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,
.tp_doc = "Preliminary NumPy API: The Type of NumPy DTypes (metaclass)",
+ .tp_getset = dtypemeta_getset,
.tp_members = dtypemeta_members,
.tp_base = NULL, /* set to PyType_Type at import time */
.tp_alloc = dtypemeta_alloc,
diff --git a/numpy/core/src/multiarray/dtypemeta.h b/numpy/core/src/multiarray/dtypemeta.h
index 83cf7c07e..200111ac2 100644
--- a/numpy/core/src/multiarray/dtypemeta.h
+++ b/numpy/core/src/multiarray/dtypemeta.h
@@ -1,7 +1,65 @@
#ifndef _NPY_DTYPEMETA_H
#define _NPY_DTYPEMETA_H
+
+/* DType flags, currently private, since we may just expose functions */
+#define NPY_DT_LEGACY 1 << 0
+#define NPY_DT_ABSTRACT 1 << 1
+#define NPY_DT_PARAMETRIC 1 << 2
+
+
+typedef struct {
+ /* DType methods, these could be moved into its own struct */
+ discover_descr_from_pyobject_function *discover_descr_from_pyobject;
+ is_known_scalar_type_function *is_known_scalar_type;
+ default_descr_function *default_descr;
+ common_dtype_function *common_dtype;
+ common_instance_function *common_instance;
+ /*
+ * The casting implementation (ArrayMethod) to convert between two
+ * instances of this DType, stored explicitly for fast access:
+ */
+ PyObject *within_dtype_castingimpl;
+ /*
+ * Dictionary of ArrayMethods representing most possible casts
+ * (structured and object are exceptions).
+ * This should potentially become a weak mapping in the future.
+ */
+ PyObject *castingimpls;
+
+ /*
+ * Storage for `descr->f`, since we may need to allow some customizatoin
+ * here at least in a transition period and we need to set it on every
+ * dtype instance for backward compatibility. (Keep this at end)
+ */
+ PyArray_ArrFuncs f;
+} NPY_DType_Slots;
+
+
#define NPY_DTYPE(descr) ((PyArray_DTypeMeta *)Py_TYPE(descr))
+#define NPY_DT_SLOTS(dtype) ((NPY_DType_Slots *)(dtype)->dt_slots)
+
+#define NPY_DT_is_legacy(dtype) ((dtype)->flags & NPY_DT_LEGACY)
+#define NPY_DT_is_abstract(dtype) ((dtype)->flags & NPY_DT_ABSTRACT)
+#define NPY_DT_is_parametric(dtype) ((dtype)->flags & NPY_DT_PARAMETRIC)
+
+/*
+ * Macros for convenient classmethod calls, since these require
+ * the DType both for the slot lookup and as first arguments.
+ *
+ * (Macros may include NULL checks where appropriate)
+ */
+#define NPY_DT_CALL_discover_descr_from_pyobject(dtype, obj) \
+ NPY_DT_SLOTS(dtype)->discover_descr_from_pyobject(dtype, obj)
+#define NPY_DT_CALL_is_known_scalar_type(dtype, obj) \
+ (NPY_DT_SLOTS(dtype)->is_known_scalar_type != NULL \
+ && NPY_DT_SLOTS(dtype)->is_known_scalar_type(dtype, obj))
+#define NPY_DT_CALL_default_descr(dtype) \
+ NPY_DT_SLOTS(dtype)->default_descr(dtype)
+#define NPY_DT_CALL_common_dtype(dtype, other) \
+ NPY_DT_SLOTS(dtype)->common_dtype(dtype, other)
+
+
/*
* This function will hopefully be phased out or replaced, but was convenient
* for incremental implementation of new DTypes based on DTypeMeta.
diff --git a/numpy/core/src/multiarray/usertypes.c b/numpy/core/src/multiarray/usertypes.c
index 15d46800c..5602304e9 100644
--- a/numpy/core/src/multiarray/usertypes.c
+++ b/numpy/core/src/multiarray/usertypes.c
@@ -388,7 +388,7 @@ legacy_userdtype_common_dtype_function(
{
int skind1 = NPY_NOSCALAR, skind2 = NPY_NOSCALAR, skind;
- if (!other->legacy) {
+ if (!NPY_DT_is_legacy(other)) {
/* legacy DTypes can always defer to new style ones */
Py_INCREF(Py_NotImplemented);
return (PyArray_DTypeMeta *)Py_NotImplemented;
@@ -422,7 +422,7 @@ legacy_userdtype_common_dtype_function(
*/
/* Convert the 'kind' char into a scalar kind */
- switch (cls->kind) {
+ switch (cls->singleton->kind) {
case 'b':
skind1 = NPY_BOOL_SCALAR;
break;
@@ -439,7 +439,7 @@ legacy_userdtype_common_dtype_function(
skind1 = NPY_COMPLEX_SCALAR;
break;
}
- switch (other->kind) {
+ switch (other->singleton->kind) {
case 'b':
skind2 = NPY_BOOL_SCALAR;
break;
diff --git a/numpy/core/src/umath/_scaled_float_dtype.c b/numpy/core/src/umath/_scaled_float_dtype.c
index aa9c4549c..599774cce 100644
--- a/numpy/core/src/umath/_scaled_float_dtype.c
+++ b/numpy/core/src/umath/_scaled_float_dtype.c
@@ -57,14 +57,14 @@ sfloat_default_descr(PyArray_DTypeMeta *NPY_UNUSED(cls))
static PyArray_Descr *
sfloat_discover_from_pyobject(PyArray_DTypeMeta *cls, PyObject *NPY_UNUSED(obj))
{
- return cls->default_descr(cls);
+ return sfloat_default_descr(cls);
}
static PyArray_DTypeMeta *
sfloat_common_dtype(PyArray_DTypeMeta *cls, PyArray_DTypeMeta *other)
{
- if (other->legacy && other->type_num == NPY_DOUBLE) {
+ if (NPY_DT_is_legacy(other) && other->type_num == NPY_DOUBLE) {
Py_INCREF(cls);
return cls;
}
@@ -123,9 +123,17 @@ sfloat_setitem(PyObject *obj, char *data, PyArrayObject *arr)
}
-static PyArray_ArrFuncs arrfuncs = {
- .getitem = (PyArray_GetItemFunc *)&sfloat_getitem,
- .setitem = (PyArray_SetItemFunc *)&sfloat_setitem,
+/* Special DType methods and the descr->f slot storage */
+NPY_DType_Slots sfloat_slots = {
+ .default_descr = &sfloat_default_descr,
+ .discover_descr_from_pyobject = &sfloat_discover_from_pyobject,
+ .is_known_scalar_type = &sfloat_is_known_scalar_type,
+ .common_dtype = &sfloat_common_dtype,
+ .common_instance = &sfloat_common_instance,
+ .f = {
+ .getitem = (PyArray_GetItemFunc *)&sfloat_getitem,
+ .setitem = (PyArray_SetItemFunc *)&sfloat_setitem,
+ }
};
@@ -134,7 +142,7 @@ static PyArray_SFloatDescr SFloatSingleton = {{
.alignment = _ALIGN(double),
.flags = NPY_USE_GETITEM|NPY_USE_SETITEM,
.type_num = -1,
- .f = &arrfuncs,
+ .f = &sfloat_slots.f,
.byteorder = '|', /* do not bother with byte-swapping... */
},
.scaling = 1,
@@ -232,17 +240,9 @@ static PyArray_DTypeMeta PyArray_SFloatDType = {{{
.tp_basicsize = sizeof(PyArray_SFloatDescr),
}},
.type_num = -1,
- .abstract = 0,
- .legacy = 0,
- .parametric = 1,
- .f = &arrfuncs,
.scalar_type = NULL,
- /* Special methods: */
- .default_descr = &sfloat_default_descr,
- .discover_descr_from_pyobject = &sfloat_discover_from_pyobject,
- .is_known_scalar_type = &sfloat_is_known_scalar_type,
- .common_dtype = &sfloat_common_dtype,
- .common_instance = &sfloat_common_instance,
+ .flags = NPY_DT_PARAMETRIC,
+ .dt_slots = &sfloat_slots,
};
@@ -386,11 +386,11 @@ float_to_from_sfloat_resolve_descriptors(
PyArray_Descr *NPY_UNUSED(given_descrs[2]),
PyArray_Descr *loop_descrs[2])
{
- loop_descrs[0] = dtypes[0]->default_descr(dtypes[0]);
+ loop_descrs[0] = NPY_DT_CALL_default_descr(dtypes[0]);
if (loop_descrs[0] == NULL) {
return -1;
}
- loop_descrs[1] = dtypes[1]->default_descr(dtypes[1]);
+ loop_descrs[1] = NPY_DT_CALL_default_descr(dtypes[1]);
if (loop_descrs[1] == NULL) {
return -1;
}
@@ -696,8 +696,8 @@ get_sfloat_dtype(PyObject *NPY_UNUSED(mod), PyObject *NPY_UNUSED(args))
if (PyType_Ready((PyTypeObject *)&PyArray_SFloatDType) < 0) {
return NULL;
}
- PyArray_SFloatDType.castingimpls = PyDict_New();
- if (PyArray_SFloatDType.castingimpls == NULL) {
+ NPY_DT_SLOTS(&PyArray_SFloatDType)->castingimpls = PyDict_New();
+ if (NPY_DT_SLOTS(&PyArray_SFloatDType)->castingimpls == NULL) {
return NULL;
}
diff --git a/numpy/core/src/umath/dispatching.c b/numpy/core/src/umath/dispatching.c
index 1d3ad9dff..b1c5ccb6b 100644
--- a/numpy/core/src/umath/dispatching.c
+++ b/numpy/core/src/umath/dispatching.c
@@ -194,7 +194,7 @@ resolve_implementation_info(PyUFuncObject *ufunc,
if (given_dtype == resolver_dtype) {
continue;
}
- if (!resolver_dtype->abstract) {
+ if (!NPY_DT_is_abstract(resolver_dtype)) {
matches = NPY_FALSE;
break;
}
@@ -208,10 +208,11 @@ resolve_implementation_info(PyUFuncObject *ufunc,
*
* Continuing here allows a promoter to handle reduce-like
* promotions explicitly if necessary.
- * TODO: The `!resolver_dtype->abstract` currently ensures that
- * this is a promoter. If we allow ArrayMethods to use
- * abstract DTypes, we may have to reject it here or the
- * ArrayMethod has to implement the reduce promotion.
+ * TODO: The `!NPY_DT_is_abstract(resolver_dtype)` currently
+ * ensures that this is a promoter. If we allow
+ * `ArrayMethods` to use abstract DTypes, we may have to
+ * reject it here or the `ArrayMethod` has to implement
+ * the reduce promotion.
*/
continue;
}
@@ -277,8 +278,8 @@ resolve_implementation_info(PyUFuncObject *ufunc,
* If both are concrete and not identical, this is
* ambiguous.
*/
- else if (!((PyArray_DTypeMeta *)prev_dtype)->abstract &&
- !((PyArray_DTypeMeta *)new_dtype)->abstract) {
+ else if (!NPY_DT_is_abstract((PyArray_DTypeMeta *)prev_dtype) &&
+ !NPY_DT_is_abstract((PyArray_DTypeMeta *)new_dtype)) {
/*
* Ambiguous unless the are identical (checked above),
* but since they are concrete it does not matter which
@@ -389,7 +390,8 @@ _make_new_typetup(
none_count++;
}
else {
- if (!signature[i]->legacy || signature[i]->abstract) {
+ if (!NPY_DT_is_legacy(signature[i])
+ || NPY_DT_is_abstract(signature[i])) {
/*
* The legacy type resolution can't deal with these.
* This path will return `None` or so in the future to
@@ -643,7 +645,7 @@ promote_and_get_ufuncimpl(PyUFuncObject *ufunc,
*/
Py_INCREF(signature[i]);
Py_XSETREF(op_dtypes[i], signature[i]);
- assert(i >= ufunc->nin || !signature[i]->abstract);
+ assert(i >= ufunc->nin || !NPY_DT_is_abstract(signature[i]));
}
}
diff --git a/numpy/core/src/umath/legacy_array_method.c b/numpy/core/src/umath/legacy_array_method.c
index e5043aa71..a5e123baa 100644
--- a/numpy/core/src/umath/legacy_array_method.c
+++ b/numpy/core/src/umath/legacy_array_method.c
@@ -14,6 +14,7 @@
#include "array_method.h"
#include "dtype_transfer.h"
#include "legacy_array_method.h"
+#include "dtypemeta.h"
typedef struct {
@@ -134,7 +135,7 @@ simple_legacy_resolve_descriptors(
output_descrs[i] = output_descrs[0];
}
else {
- output_descrs[i] = dtypes[i]->default_descr(dtypes[i]);
+ output_descrs[i] = NPY_DT_CALL_default_descr(dtypes[i]);
}
if (output_descrs[i] == NULL) {
goto fail;
@@ -221,7 +222,7 @@ PyArray_NewLegacyWrappingArrayMethod(PyUFuncObject *ufunc,
NPY_ITEM_REFCOUNT | NPY_ITEM_IS_POINTER | NPY_NEEDS_PYAPI)) {
flags |= NPY_METH_REQUIRES_PYAPI;
}
- if (signature[i]->parametric) {
+ if (NPY_DT_is_parametric(signature[i])) {
any_output_flexible = 1;
}
}
diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 5a32ae603..bed303a86 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -957,7 +957,7 @@ convert_ufunc_arguments(PyUFuncObject *ufunc,
out_op_DTypes[i] = NPY_DTYPE(PyArray_DESCR(out_op[i]));
Py_INCREF(out_op_DTypes[i]);
- if (!out_op_DTypes[i]->legacy) {
+ if (!NPY_DT_is_legacy(out_op_DTypes[i])) {
*allow_legacy_promotion = NPY_FALSE;
}
if (PyArray_NDIM(out_op[i]) == 0) {
@@ -4274,7 +4274,7 @@ _get_dtype(PyObject *dtype_obj) {
return NULL;
}
PyArray_DTypeMeta *out = NPY_DTYPE(descr);
- if (NPY_UNLIKELY(!out->legacy)) {
+ if (NPY_UNLIKELY(!NPY_DT_is_legacy(out))) {
/* TODO: this path was unreachable when added. */
PyErr_SetString(PyExc_TypeError,
"Cannot pass a new user DType instance to the `dtype` or "
@@ -4392,7 +4392,7 @@ _get_fixed_signature(PyUFuncObject *ufunc,
if (signature[i] == NULL) {
return -1;
}
- else if (i < nin && signature[i]->abstract) {
+ else if (i < nin && NPY_DT_is_abstract(signature[i])) {
/*
* We reject abstract input signatures for now. These
* can probably be defined by finding the common DType with
diff --git a/numpy/core/tests/test_array_coercion.py b/numpy/core/tests/test_array_coercion.py
index 45c792ad2..076d8e43f 100644
--- a/numpy/core/tests/test_array_coercion.py
+++ b/numpy/core/tests/test_array_coercion.py
@@ -342,6 +342,20 @@ class TestScalarDiscovery:
ass[()] = scalar
assert_array_equal(ass, cast)
+ @pytest.mark.parametrize("pyscalar", [10, 10.32, 10.14j, 10**100])
+ def test_pyscalar_subclasses(self, pyscalar):
+ """NumPy arrays are read/write which means that anything but invariant
+ behaviour is on thin ice. However, we currently are happy to discover
+ subclasses of Python float, int, complex the same as the base classes.
+ This should potentially be deprecated.
+ """
+ class MyScalar(type(pyscalar)):
+ pass
+
+ res = np.array(MyScalar(pyscalar))
+ expected = np.array(pyscalar)
+ assert_array_equal(res, expected)
+
@pytest.mark.parametrize("dtype_char", np.typecodes["All"])
def test_default_dtype_instance(self, dtype_char):
if dtype_char in "SU":
diff --git a/numpy/core/tests/test_casting_unittests.py b/numpy/core/tests/test_casting_unittests.py
index 8398b3cad..3f67f1832 100644
--- a/numpy/core/tests/test_casting_unittests.py
+++ b/numpy/core/tests/test_casting_unittests.py
@@ -650,6 +650,30 @@ class TestCasting:
match="casting from object to the parametric DType"):
cast._resolve_descriptors((np.dtype("O"), None))
+ @pytest.mark.parametrize("dtype", simple_dtype_instances())
+ def test_object_and_simple_resolution(self, dtype):
+ # Simple test to exercise the cast when no instance is specified
+ object_dtype = type(np.dtype(object))
+ cast = get_castingimpl(object_dtype, type(dtype))
+
+ safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), dtype))
+ assert safety == Casting.unsafe
+ assert res_dt is dtype
+
+ safety, (_, res_dt) = cast._resolve_descriptors((np.dtype("O"), None))
+ assert safety == Casting.unsafe
+ assert res_dt == dtype.newbyteorder("=")
+
+ @pytest.mark.parametrize("dtype", simple_dtype_instances())
+ def test_simple_to_object_resolution(self, dtype):
+ # Simple test to exercise the cast when no instance is specified
+ object_dtype = type(np.dtype(object))
+ cast = get_castingimpl(type(dtype), object_dtype)
+
+ safety, (_, res_dt) = cast._resolve_descriptors((dtype, None))
+ assert safety == Casting.safe
+ assert res_dt is np.dtype("O")
+
@pytest.mark.parametrize("casting", ["no", "unsafe"])
def test_void_and_structured_with_subarray(self, casting):
# test case corresponding to gh-19325