diff options
Diffstat (limited to 'numpy')
-rw-r--r-- | numpy/core/include/numpy/ndarraytypes.h | 54 | ||||
-rw-r--r-- | numpy/core/src/multiarray/_multiarray_tests.c.src | 6 | ||||
-rw-r--r-- | numpy/core/src/multiarray/abstractdtypes.c | 53 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_coercion.c | 17 | ||||
-rw-r--r-- | numpy/core/src/multiarray/array_method.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common.c | 4 | ||||
-rw-r--r-- | numpy/core/src/multiarray/common_dtype.c | 10 | ||||
-rw-r--r-- | numpy/core/src/multiarray/convert_datatype.c | 94 | ||||
-rw-r--r-- | numpy/core/src/multiarray/datetime.c | 2 | ||||
-rw-r--r-- | numpy/core/src/multiarray/descriptor.c | 7 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtypemeta.c | 110 | ||||
-rw-r--r-- | numpy/core/src/multiarray/dtypemeta.h | 58 | ||||
-rw-r--r-- | numpy/core/src/multiarray/usertypes.c | 6 | ||||
-rw-r--r-- | numpy/core/src/umath/_scaled_float_dtype.c | 40 | ||||
-rw-r--r-- | numpy/core/src/umath/dispatching.c | 20 | ||||
-rw-r--r-- | numpy/core/src/umath/legacy_array_method.c | 5 | ||||
-rw-r--r-- | numpy/core/src/umath/ufunc_object.c | 6 | ||||
-rw-r--r-- | numpy/core/tests/test_array_coercion.py | 14 | ||||
-rw-r--r-- | numpy/core/tests/test_casting_unittests.py | 24 |
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 |