summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/release/upcoming_changes/14933.compatibility.rst10
-rw-r--r--doc/source/reference/c-api/array.rst20
-rw-r--r--numpy/core/src/multiarray/convert_datatype.c105
-rw-r--r--numpy/core/tests/test_multiarray.py9
4 files changed, 59 insertions, 85 deletions
diff --git a/doc/release/upcoming_changes/14933.compatibility.rst b/doc/release/upcoming_changes/14933.compatibility.rst
new file mode 100644
index 000000000..b939fec7f
--- /dev/null
+++ b/doc/release/upcoming_changes/14933.compatibility.rst
@@ -0,0 +1,10 @@
+Scalar promotion in ``PyArray_ConvertToCommonType``
+---------------------------------------------------
+
+The promotion of mixed scalars and arrays in ``PyArray_ConvertToCommonType``
+has been changed to adhere to those used by ``np.result_type``.
+This means that input such as ``(1000, np.array([1], dtype=np.uint8)))``
+will now return ``uint16`` dtypes. In most cases the behaviour is unchanged.
+Note that the use of this C-API function is generally discouarged.
+This also fixes ``np.choose`` to behave the same way as the rest of NumPy
+in this respect.
diff --git a/doc/source/reference/c-api/array.rst b/doc/source/reference/c-api/array.rst
index 0530a5747..c910efa60 100644
--- a/doc/source/reference/c-api/array.rst
+++ b/doc/source/reference/c-api/array.rst
@@ -1255,14 +1255,18 @@ Converting data types
Convert a sequence of Python objects contained in *op* to an array
of ndarrays each having the same data type. The type is selected
- based on the typenumber (larger type number is chosen over a
- smaller one) ignoring objects that are only scalars. The length of
- the sequence is returned in *n*, and an *n* -length array of
- :c:type:`PyArrayObject` pointers is the return value (or ``NULL`` if an
- error occurs). The returned array must be freed by the caller of
- this routine (using :c:func:`PyDataMem_FREE` ) and all the array objects
- in it ``DECREF`` 'd or a memory-leak will occur. The example
- template-code below shows a typically usage:
+ in the same way as `PyArray_ResultType`. The length of the sequence is
+ returned in *n*, and an *n* -length array of :c:type:`PyArrayObject`
+ pointers is the return value (or ``NULL`` if an error occurs).
+ The returned array must be freed by the caller of this routine
+ (using :c:func:`PyDataMem_FREE` ) and all the array objects in it
+ ``DECREF`` 'd or a memory-leak will occur. The example template-code
+ below shows a typically usage:
+
+ .. versionchanged:: 1.18.0
+ A mix of scalars and zero-dimensional arrays now produces a type
+ capable of holding the scalar value.
+ Previously priority was given to the dtype of the arrays.
.. code-block:: c
diff --git a/numpy/core/src/multiarray/convert_datatype.c b/numpy/core/src/multiarray/convert_datatype.c
index 4326448dc..d4774c2b2 100644
--- a/numpy/core/src/multiarray/convert_datatype.c
+++ b/numpy/core/src/multiarray/convert_datatype.c
@@ -2121,15 +2121,19 @@ PyArray_ObjectType(PyObject *op, int minimum_type)
/* Raises error when len(op) == 0 */
-/*NUMPY_API*/
+/*NUMPY_API
+ *
+ * This function is only used in one place within NumPy and should
+ * generally be avoided. It is provided mainly for backward compatibility.
+ *
+ * The user of the function has to free the returned array.
+ */
NPY_NO_EXPORT PyArrayObject **
PyArray_ConvertToCommonType(PyObject *op, int *retn)
{
- int i, n, allscalars = 0;
+ int i, n;
+ PyArray_Descr *common_descr = NULL;
PyArrayObject **mps = NULL;
- PyArray_Descr *intype = NULL, *stype = NULL;
- PyArray_Descr *newtype = NULL;
- NPY_SCALARKIND scalarkind = NPY_NOSCALAR, intypekind = NPY_NOSCALAR;
*retn = n = PySequence_Length(op);
if (n == 0) {
@@ -2165,94 +2169,41 @@ PyArray_ConvertToCommonType(PyObject *op, int *retn)
}
for (i = 0; i < n; i++) {
- PyObject *otmp = PySequence_GetItem(op, i);
- if (otmp == NULL) {
+ /* Convert everything to an array, this could be optimized away */
+ PyObject *tmp = PySequence_GetItem(op, i);
+ if (tmp == NULL) {
goto fail;
}
- if (!PyArray_CheckAnyScalar(otmp)) {
- newtype = PyArray_DescrFromObject(otmp, intype);
- Py_DECREF(otmp);
- Py_XDECREF(intype);
- if (newtype == NULL) {
- goto fail;
- }
- intype = newtype;
- intypekind = PyArray_ScalarKind(intype->type_num, NULL);
- }
- else {
- newtype = PyArray_DescrFromObject(otmp, stype);
- Py_DECREF(otmp);
- Py_XDECREF(stype);
- if (newtype == NULL) {
- goto fail;
- }
- stype = newtype;
- scalarkind = PyArray_ScalarKind(newtype->type_num, NULL);
- mps[i] = (PyArrayObject *)Py_None;
- Py_INCREF(Py_None);
- }
- }
- if (intype == NULL) {
- /* all scalars */
- allscalars = 1;
- intype = stype;
- Py_INCREF(intype);
- for (i = 0; i < n; i++) {
- Py_XDECREF(mps[i]);
- mps[i] = NULL;
- }
- }
- else if ((stype != NULL) && (intypekind != scalarkind)) {
- /*
- * we need to upconvert to type that
- * handles both intype and stype
- * also don't forcecast the scalars.
- */
- if (!PyArray_CanCoerceScalar(stype->type_num,
- intype->type_num,
- scalarkind)) {
- newtype = PyArray_PromoteTypes(intype, stype);
- Py_XDECREF(intype);
- intype = newtype;
- if (newtype == NULL) {
- goto fail;
- }
- }
- for (i = 0; i < n; i++) {
- Py_XDECREF(mps[i]);
- mps[i] = NULL;
+
+ mps[i] = (PyArrayObject *)PyArray_FROM_O(tmp);
+ Py_DECREF(tmp);
+ if (mps[i] == NULL) {
+ goto fail;
}
}
+ common_descr = PyArray_ResultType(n, mps, 0, NULL);
+ if (common_descr == NULL) {
+ goto fail;
+ }
- /* Make sure all arrays are actual array objects. */
+ /* Make sure all arrays are contiguous and have the correct dtype. */
for (i = 0; i < n; i++) {
int flags = NPY_ARRAY_CARRAY;
- PyObject *otmp = PySequence_GetItem(op, i);
+ PyArrayObject *tmp = mps[i];
- if (otmp == NULL) {
- goto fail;
- }
- if (!allscalars && ((PyObject *)(mps[i]) == Py_None)) {
- /* forcecast scalars */
- flags |= NPY_ARRAY_FORCECAST;
- Py_DECREF(Py_None);
- }
- Py_INCREF(intype);
- mps[i] = (PyArrayObject*)PyArray_FromAny(otmp, intype, 0, 0,
- flags, NULL);
- Py_DECREF(otmp);
+ Py_INCREF(common_descr);
+ mps[i] = (PyArrayObject *)PyArray_FromArray(tmp, common_descr, flags);
+ Py_DECREF(tmp);
if (mps[i] == NULL) {
goto fail;
}
}
- Py_DECREF(intype);
- Py_XDECREF(stype);
+ Py_DECREF(common_descr);
return mps;
fail:
- Py_XDECREF(intype);
- Py_XDECREF(stype);
+ Py_XDECREF(common_descr);
*retn = 0;
for (i = 0; i < n; i++) {
Py_XDECREF(mps[i]);
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 22d550ecc..ae4e89d5b 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -6465,6 +6465,15 @@ class TestChoose(object):
A = np.choose(self.ind, (self.x, self.y2))
assert_equal(A, [[2, 2, 3], [2, 2, 3]])
+ @pytest.mark.parametrize("ops",
+ [(1000, np.array([1], dtype=np.uint8)),
+ (-1, np.array([1], dtype=np.uint8)),
+ (1., np.float32(3)),
+ (1., np.array([3], dtype=np.float32))],)
+ def test_output_dtype(self, ops):
+ expected_dt = np.result_type(*ops)
+ assert(np.choose([0], ops).dtype == expected_dt)
+
class TestRepeat(object):
def setup(self):