summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatti Picus <matti.picus@gmail.com>2022-09-07 12:55:38 -0400
committerGitHub <noreply@github.com>2022-09-07 12:55:38 -0400
commit65c10c1cfecb2b3ebd0306ed1c63b103158f80d5 (patch)
treec7189d614ac713d7df0399cd452d98c5562ba912
parent0e960b985843ff99db06f89eadaa9f387b5a65f8 (diff)
parent235b75e939b8233f29a073175d93070fcfc5e8d1 (diff)
downloadnumpy-65c10c1cfecb2b3ebd0306ed1c63b103158f80d5.tar.gz
Merge pull request #21995 from eirrgang/mei-1468
BUG: Distinguish exact vs. equivalent dtype for C type aliases.
-rw-r--r--doc/release/upcoming_changes/21995.compatibility.rst21
-rw-r--r--numpy/array_api/tests/test_asarray.py64
-rw-r--r--numpy/core/src/multiarray/multiarraymodule.c23
-rw-r--r--numpy/ma/tests/test_core.py2
4 files changed, 107 insertions, 3 deletions
diff --git a/doc/release/upcoming_changes/21995.compatibility.rst b/doc/release/upcoming_changes/21995.compatibility.rst
new file mode 100644
index 000000000..d3ff9c1bd
--- /dev/null
+++ b/doc/release/upcoming_changes/21995.compatibility.rst
@@ -0,0 +1,21 @@
+Returned arrays respect uniqueness of dtype kwarg objects
+---------------------------------------------------------
+When the ``dtype`` keyword argument is used with :py:func:`np.array()`
+or :py:func:`asarray()`, the dtype of the returned array now
+always exactly matches the dtype provided by the caller.
+
+In some cases this change means that a *view* rather than the
+input array is returned.
+The following is an example for this on 64bit Linux where ``long``
+and ``longlong`` are the same precision::
+
+ >>> arr = np.array([1, 2, 3], dtype="long")
+ >>> new_dtype = np.dtype("longlong")
+ >>> new = np.asarray(arr, dtype=new_dtype)
+ >>> new.dtype is dtype
+ True
+ >>> new is arr
+ False
+
+Before the change, the ``dtype`` did not match because ``new is arr``
+was true.
diff --git a/numpy/array_api/tests/test_asarray.py b/numpy/array_api/tests/test_asarray.py
new file mode 100644
index 000000000..5c269823f
--- /dev/null
+++ b/numpy/array_api/tests/test_asarray.py
@@ -0,0 +1,64 @@
+import itertools
+
+import numpy as np
+
+
+def test_dtype_identity():
+ """Confirm the intended behavior for ``asarray`` results.
+
+ The result of ``asarray()`` should have the dtype provided through the
+ keyword argument, when used. This forces unique array handles to be
+ produced for unique np.dtype objects, but (for equivalent dtypes), the
+ underlying data (the base object) is shared with the original array object.
+
+ Ref https://github.com/numpy/numpy/issues/1468
+ """
+ int_array = np.array([1, 2, 3], dtype='i')
+ assert np.asarray(int_array) is int_array
+
+ # The character code resolves to the singleton dtype object provided
+ # by the numpy package.
+ assert np.asarray(int_array, dtype='i') is int_array
+
+ # Derive a dtype from n.dtype('i'), but add a metadata object to force
+ # the dtype to be distinct.
+ unequal_type = np.dtype('i', metadata={'spam': True})
+ annotated_int_array = np.asarray(int_array, dtype=unequal_type)
+ assert annotated_int_array is not int_array
+ assert annotated_int_array.base is int_array
+ # Create an equivalent descriptor with a new and distinct dtype instance.
+ equivalent_requirement = np.dtype('i', metadata={'spam': True})
+ annotated_int_array_alt = np.asarray(annotated_int_array,
+ dtype=equivalent_requirement)
+ assert unequal_type == equivalent_requirement
+ assert unequal_type is not equivalent_requirement
+ assert annotated_int_array_alt is not annotated_int_array
+ assert annotated_int_array_alt.dtype is equivalent_requirement
+
+ # Check the same logic for a pair of C types whose equivalence may vary
+ # between computing environments.
+ # Find an equivalent pair.
+ integer_type_codes = ('i', 'l', 'q')
+ integer_dtypes = [np.dtype(code) for code in integer_type_codes]
+ typeA = None
+ typeB = None
+ for typeA, typeB in itertools.permutations(integer_dtypes, r=2):
+ if typeA == typeB:
+ assert typeA is not typeB
+ break
+ assert isinstance(typeA, np.dtype) and isinstance(typeB, np.dtype)
+
+ # These ``asarray()`` calls may produce a new view or a copy,
+ # but never the same object.
+ long_int_array = np.asarray(int_array, dtype='l')
+ long_long_int_array = np.asarray(int_array, dtype='q')
+ assert long_int_array is not int_array
+ assert long_long_int_array is not int_array
+ assert np.asarray(long_int_array, dtype='q') is not long_int_array
+ array_a = np.asarray(int_array, dtype=typeA)
+ assert typeA == typeB
+ assert typeA is not typeB
+ assert array_a.dtype is typeA
+ assert array_a is not np.asarray(array_a, dtype=typeB)
+ assert np.asarray(array_a, dtype=typeB).dtype is typeB
+ assert array_a is np.asarray(array_a, dtype=typeB).base
diff --git a/numpy/core/src/multiarray/multiarraymodule.c b/numpy/core/src/multiarray/multiarraymodule.c
index 18a572e51..f01752431 100644
--- a/numpy/core/src/multiarray/multiarraymodule.c
+++ b/numpy/core/src/multiarray/multiarraymodule.c
@@ -1627,12 +1627,31 @@ _array_fromobject_generic(
goto finish;
}
}
- /* One more chance */
+ /* One more chance for faster exit if user specified the dtype. */
oldtype = PyArray_DESCR(oparr);
if (PyArray_EquivTypes(oldtype, type)) {
if (copy != NPY_COPY_ALWAYS && STRIDING_OK(oparr, order)) {
Py_INCREF(op);
- ret = oparr;
+ if (oldtype == type) {
+ ret = oparr;
+ }
+ else {
+ /* Create a new PyArrayObject from the caller's
+ * PyArray_Descr. Use the reference `op` as the base
+ * object. */
+ Py_INCREF(type);
+ ret = (PyArrayObject *)PyArray_NewFromDescrAndBase(
+ Py_TYPE(op),
+ type,
+ PyArray_NDIM(oparr),
+ PyArray_DIMS(oparr),
+ PyArray_STRIDES(oparr),
+ PyArray_DATA(oparr),
+ PyArray_FLAGS(oparr),
+ op,
+ op
+ );
+ }
goto finish;
}
else {
diff --git a/numpy/ma/tests/test_core.py b/numpy/ma/tests/test_core.py
index b056d5169..3997f44e3 100644
--- a/numpy/ma/tests/test_core.py
+++ b/numpy/ma/tests/test_core.py
@@ -5387,7 +5387,7 @@ def test_ufunc_with_out_varied():
def test_astype_mask_ordering():
- descr = [('v', int, 3), ('x', [('y', float)])]
+ descr = np.dtype([('v', int, 3), ('x', [('y', float)])])
x = array([
[([1, 2, 3], (1.0,)), ([1, 2, 3], (2.0,))],
[([1, 2, 3], (3.0,)), ([1, 2, 3], (4.0,))]], dtype=descr)