From bc88f60a15c4f312cea1e2ab06e7f2d05908218a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Thu, 10 Apr 2025 11:47:50 +0000 Subject: [PATCH 1/4] MAINT: Deprecate `.T` property for non-2dim arrays and scalars --- .../upcoming_changes/28678.deprecation.rst | 2 + numpy/_core/_add_newdocs.py | 9 +--- numpy/_core/src/multiarray/getset.c | 9 ++++ numpy/_core/src/multiarray/scalartypes.c.src | 5 +++ numpy/_core/tests/test_deprecations.py | 14 ++++++ numpy/_core/tests/test_einsum.py | 20 ++++----- numpy/_core/tests/test_indexing.py | 4 +- numpy/_core/tests/test_multiarray.py | 20 ++++----- numpy/_core/tests/test_nditer.py | 43 ++++++++++--------- numpy/_core/tests/test_regression.py | 4 +- numpy/_core/tests/test_shape_base.py | 5 ++- numpy/_core/tests/test_ufunc.py | 4 +- numpy/fft/tests/test_pocketfft.py | 2 +- numpy/lib/_function_base_impl.py | 2 +- numpy/lib/_polynomial_impl.py | 2 +- numpy/lib/tests/test_stride_tricks.py | 4 +- numpy/polynomial/polyutils.py | 9 ++-- 17 files changed, 94 insertions(+), 64 deletions(-) create mode 100644 doc/release/upcoming_changes/28678.deprecation.rst diff --git a/doc/release/upcoming_changes/28678.deprecation.rst b/doc/release/upcoming_changes/28678.deprecation.rst new file mode 100644 index 000000000000..4b83a39ce046 --- /dev/null +++ b/doc/release/upcoming_changes/28678.deprecation.rst @@ -0,0 +1,2 @@ +* ``arr.T`` property has been deprecated for array scalars and arrays with + dimensionality different than ``2``. diff --git a/numpy/_core/_add_newdocs.py b/numpy/_core/_add_newdocs.py index 8f5de4b7bd89..2be89882fc19 100644 --- a/numpy/_core/_add_newdocs.py +++ b/numpy/_core/_add_newdocs.py @@ -2887,7 +2887,8 @@ """ View of the transposed array. - Same as ``self.transpose()``. + Same as ``self.transpose()`` except that it requires + the array to be 2-dimensional. Examples -------- @@ -2900,12 +2901,6 @@ array([[1, 3], [2, 4]]) - >>> a = np.array([1, 2, 3, 4]) - >>> a - array([1, 2, 3, 4]) - >>> a.T - array([1, 2, 3, 4]) - See Also -------- transpose diff --git a/numpy/_core/src/multiarray/getset.c b/numpy/_core/src/multiarray/getset.c index 8482b6006e3e..9b2c3384bb0a 100644 --- a/numpy/_core/src/multiarray/getset.c +++ b/numpy/_core/src/multiarray/getset.c @@ -848,6 +848,15 @@ array_flat_set(PyArrayObject *self, PyObject *val, void *NPY_UNUSED(ignored)) static PyObject * array_transpose_get(PyArrayObject *self, void *NPY_UNUSED(ignored)) { + int ndim = PyArray_NDIM(self); + if (ndim != 2) { + if (PyErr_WarnFormat(PyExc_UserWarning, 1, + "In the future `.T` property will be supported for " + "2-dim arrays only. Here it is %d-dim array.", + ndim) < 0) { + return NULL; + } + } return PyArray_Transpose(self, NULL); } diff --git a/numpy/_core/src/multiarray/scalartypes.c.src b/numpy/_core/src/multiarray/scalartypes.c.src index 03165b10337e..b3e35ffdc685 100644 --- a/numpy/_core/src/multiarray/scalartypes.c.src +++ b/numpy/_core/src/multiarray/scalartypes.c.src @@ -1925,6 +1925,11 @@ gentype_flat_get(PyObject *self, void *NPY_UNUSED(ignored)) static PyObject * gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored)) { + if (PyErr_WarnEx(PyExc_UserWarning, + "In the future `.T` property for array scalars will " + "raise an error.", 1) < 0) { + return NULL; + } Py_INCREF(self); return self; } diff --git a/numpy/_core/tests/test_deprecations.py b/numpy/_core/tests/test_deprecations.py index d90c15565c22..41791149ba1e 100644 --- a/numpy/_core/tests/test_deprecations.py +++ b/numpy/_core/tests/test_deprecations.py @@ -452,3 +452,17 @@ def test_deprecated(self): struct_ufunc.add_triplet, "new docs" ) ) + + +def test_deprecated_T_non_2dim(): + # Deprecated in Numpy 2.3, 2025-04 + with pytest.warns(UserWarning, match="In the future `.T` property for " + "array scalars will raise an error."): + np.int64(1).T + for shape in [(5,), (2, 3, 4)]: + with pytest.warns( + UserWarning, + match="In the future `.T` property will be " + "supported for 2-dim arrays only. " + f"Here it is {len(shape)}-dim array."): + np.ones(shape).T diff --git a/numpy/_core/tests/test_einsum.py b/numpy/_core/tests/test_einsum.py index 0bd180b5e41f..cfe89858daf9 100644 --- a/numpy/_core/tests/test_einsum.py +++ b/numpy/_core/tests/test_einsum.py @@ -441,9 +441,9 @@ def check_einsum_sums(self, dtype, do_opt=False): a = np.arange(n * 3 * 2, dtype=dtype).reshape(n, 3, 2) b = np.arange(n, dtype=dtype) assert_equal(np.einsum("i..., i...", a, b, optimize=do_opt), - np.inner(a.T, b.T).T) + np.inner(a.transpose(), b.transpose()).transpose()) assert_equal(np.einsum(a, [0, Ellipsis], b, [0, Ellipsis], optimize=do_opt), - np.inner(a.T, b.T).T) + np.inner(a.transpose(), b.transpose()).transpose()) # outer(a,b) for n in range(1, 17): @@ -483,22 +483,22 @@ def check_einsum_sums(self, dtype, do_opt=False): for n in range(1, 17): a = np.arange(4 * n, dtype=dtype).reshape(4, n) b = np.arange(n, dtype=dtype) - assert_equal(np.einsum("ji,j", a.T, b.T, optimize=do_opt), - np.dot(b.T, a.T)) - assert_equal(np.einsum(a.T, [1, 0], b.T, [1], optimize=do_opt), - np.dot(b.T, a.T)) + assert_equal(np.einsum("ji,j", a.T, b, optimize=do_opt), + np.dot(b, a.T)) + assert_equal(np.einsum(a.T, [1, 0], b, [1], optimize=do_opt), + np.dot(b, a.T)) c = np.arange(4, dtype=dtype) - np.einsum("ji,j", a.T, b.T, out=c, + np.einsum("ji,j", a.T, b, out=c, dtype='f8', casting='unsafe', optimize=do_opt) assert_equal(c, - np.dot(b.T.astype('f8'), + np.dot(b.astype('f8'), a.T.astype('f8')).astype(dtype)) c[...] = 0 - np.einsum(a.T, [1, 0], b.T, [1], out=c, + np.einsum(a.T, [1, 0], b, [1], out=c, dtype='f8', casting='unsafe', optimize=do_opt) assert_equal(c, - np.dot(b.T.astype('f8'), + np.dot(b.astype('f8'), a.T.astype('f8')).astype(dtype)) # matmat(a,b) / a.dot(b) where a is matrix, b is matrix diff --git a/numpy/_core/tests/test_indexing.py b/numpy/_core/tests/test_indexing.py index e722d0c1a9df..78ed74f525d5 100644 --- a/numpy/_core/tests/test_indexing.py +++ b/numpy/_core/tests/test_indexing.py @@ -331,8 +331,8 @@ def test_uncontiguous_subspace_assignment(self): a = np.full((3, 4, 2), -1) b = np.full((3, 4, 2), -1) - a[[0, 1]] = np.arange(2 * 4 * 2).reshape(2, 4, 2).T - b[[0, 1]] = np.arange(2 * 4 * 2).reshape(2, 4, 2).T.copy() + a[[0, 1]] = np.arange(2 * 4 * 2).reshape(2, 4, 2).transpose() + b[[0, 1]] = np.arange(2 * 4 * 2).reshape(2, 4, 2).transpose().copy() assert_equal(a, b) diff --git a/numpy/_core/tests/test_multiarray.py b/numpy/_core/tests/test_multiarray.py index e0f60090656d..6ff29a854b06 100644 --- a/numpy/_core/tests/test_multiarray.py +++ b/numpy/_core/tests/test_multiarray.py @@ -3235,7 +3235,7 @@ def test_flatten(self): assert_equal(x0.flatten('F'), x0.T.flatten()) assert_equal(x1.flatten(), y1) assert_equal(x1.flatten('F'), y1f) - assert_equal(x1.flatten('F'), x1.T.flatten()) + assert_equal(x1.flatten('F'), x1.transpose().flatten()) @pytest.mark.parametrize('func', (np.dot, np.matmul)) def test_arr_mult(self, func): @@ -3340,8 +3340,8 @@ def test_no_dgemv(self, func, dtype): ret2 = func(a.copy(), b.copy()) assert_equal(ret1, ret2) - ret1 = func(b.T, a.T) - ret2 = func(b.T.copy(), a.T.copy()) + ret1 = func(b.transpose(), a.transpose()) + ret2 = func(b.transpose().copy(), a.transpose().copy()) assert_equal(ret1, ret2) def test_dot(self): @@ -4678,7 +4678,7 @@ def test_np_argmin_argmax_keepdims(self, size, axis, method): wrong_shape[0] = 2 wrong_outarray = np.empty(wrong_shape, dtype=res.dtype) with pytest.raises(ValueError): - method(arr.T, axis=axis, + method(arr.transpose(), axis=axis, out=wrong_outarray, keepdims=True) # non-contiguous arrays @@ -4689,14 +4689,14 @@ def test_np_argmin_argmax_keepdims(self, size, axis, method): new_shape[axis] = 1 new_shape = tuple(new_shape) - _res_orig = method(arr.T, axis=axis) + _res_orig = method(arr.transpose(), axis=axis) res_orig = _res_orig.reshape(new_shape) - res = method(arr.T, axis=axis, keepdims=True) + res = method(arr.transpose(), axis=axis, keepdims=True) assert_equal(res, res_orig) assert_(res.shape == new_shape) outarray = np.empty(new_shape[::-1], dtype=res.dtype) - outarray = outarray.T - res1 = method(arr.T, axis=axis, out=outarray, + outarray = outarray.transpose() + res1 = method(arr.transpose(), axis=axis, out=outarray, keepdims=True) assert_(res1 is outarray) assert_equal(res, outarray) @@ -4716,7 +4716,7 @@ def test_np_argmin_argmax_keepdims(self, size, axis, method): wrong_shape[0] = 2 wrong_outarray = np.empty(wrong_shape, dtype=res.dtype) with pytest.raises(ValueError): - method(arr.T, axis=axis, + method(arr.transpose(), axis=axis, out=wrong_outarray, keepdims=True) @pytest.mark.parametrize('method', ['max', 'min']) @@ -8345,7 +8345,7 @@ def test_relaxed_strides(self, c=np.ones((1, 10, 10), dtype='i8')): # noqa: B00 fd = io.BytesIO() fd.write(c.data) - fortran = c.T + fortran = c.transpose() assert_(memoryview(fortran).strides == (8, 80, 800)) arr = np.ones((1, 10)) diff --git a/numpy/_core/tests/test_nditer.py b/numpy/_core/tests/test_nditer.py index ec28e48c5046..56322c189b1e 100644 --- a/numpy/_core/tests/test_nditer.py +++ b/numpy/_core/tests/test_nditer.py @@ -104,7 +104,7 @@ def test_iter_best_order(): i = nditer(aview, [], [['readonly']]) assert_equal(list(i), a) # Fortran-order - i = nditer(aview.T, [], [['readonly']]) + i = nditer(aview.transpose(), [], [['readonly']]) assert_equal(list(i), a) # Other order if len(shape) > 2: @@ -130,8 +130,8 @@ def test_iter_c_order(): i = nditer(aview, order='C') assert_equal(list(i), aview.ravel(order='C')) # Fortran-order - i = nditer(aview.T, order='C') - assert_equal(list(i), aview.T.ravel(order='C')) + i = nditer(aview.transpose(), order='C') + assert_equal(list(i), aview.transpose().ravel(order='C')) # Other order if len(shape) > 2: i = nditer(aview.swapaxes(0, 1), order='C') @@ -157,8 +157,8 @@ def test_iter_f_order(): i = nditer(aview, order='F') assert_equal(list(i), aview.ravel(order='F')) # Fortran-order - i = nditer(aview.T, order='F') - assert_equal(list(i), aview.T.ravel(order='F')) + i = nditer(aview.transpose(), order='F') + assert_equal(list(i), aview.transpose().ravel(order='F')) # Other order if len(shape) > 2: i = nditer(aview.swapaxes(0, 1), order='F') @@ -184,8 +184,8 @@ def test_iter_c_or_f_order(): i = nditer(aview, order='A') assert_equal(list(i), aview.ravel(order='A')) # Fortran-order - i = nditer(aview.T, order='A') - assert_equal(list(i), aview.T.ravel(order='A')) + i = nditer(aview.transpose(), order='A') + assert_equal(list(i), aview.transpose().ravel(order='A')) # Other order if len(shape) > 2: i = nditer(aview.swapaxes(0, 1), order='A') @@ -471,7 +471,7 @@ def test_iter_no_inner_full_coalesce(): assert_equal(i.ndim, 1) assert_equal(i[0].shape, (size,)) # Fortran-order - i = nditer(aview.T, ['external_loop'], [['readonly']]) + i = nditer(aview.transpose(), ['external_loop'], [['readonly']]) assert_equal(i.ndim, 1) assert_equal(i[0].shape, (size,)) # Other order @@ -519,26 +519,26 @@ def test_iter_dim_coalescing(): assert_equal(i.ndim, 1) i = nditer(a3d.swapaxes(0, 1), ['c_index'], [['readonly']]) assert_equal(i.ndim, 3) - i = nditer(a3d.T, ['c_index'], [['readonly']]) + i = nditer(a3d.transpose(), ['c_index'], [['readonly']]) assert_equal(i.ndim, 3) - i = nditer(a3d.T, ['f_index'], [['readonly']]) + i = nditer(a3d.transpose(), ['f_index'], [['readonly']]) assert_equal(i.ndim, 1) - i = nditer(a3d.T.swapaxes(0, 1), ['f_index'], [['readonly']]) + i = nditer(a3d.transpose().swapaxes(0, 1), ['f_index'], [['readonly']]) assert_equal(i.ndim, 3) # When C or F order is forced, coalescing may still occur a3d = arange(24).reshape(2, 3, 4) i = nditer(a3d, order='C') assert_equal(i.ndim, 1) - i = nditer(a3d.T, order='C') + i = nditer(a3d.transpose(), order='C') assert_equal(i.ndim, 3) i = nditer(a3d, order='F') assert_equal(i.ndim, 3) - i = nditer(a3d.T, order='F') + i = nditer(a3d.transpose(), order='F') assert_equal(i.ndim, 1) i = nditer(a3d, order='A') assert_equal(i.ndim, 1) - i = nditer(a3d.T, order='A') + i = nditer(a3d.transpose(), order='A') assert_equal(i.ndim, 1) def test_iter_broadcasting(): @@ -804,7 +804,7 @@ def test_iter_slice(): assert_equal(i[0:2], [3, 12]) def test_iter_assign_mapping(): - a = np.arange(24, dtype='f8').reshape(2, 3, 4).T + a = np.arange(24, dtype='f8').reshape(2, 3, 4).transpose() it = np.nditer(a, [], [['readwrite', 'updateifcopy']], casting='same_kind', op_dtypes=[np.dtype('f4')]) with it: @@ -923,7 +923,7 @@ def test_iter_array_cast(): assert_equal(i.operands[0].strides, (96, 8, 32)) # Same-kind cast 'f8' -> 'f4' -> 'f8' - a = np.arange(24, dtype='f8').reshape(2, 3, 4).T + a = np.arange(24, dtype='f8').reshape(2, 3, 4).transpose() with nditer(a, [], [['readwrite', 'updateifcopy']], casting='same_kind', @@ -1297,7 +1297,8 @@ def test_iter_op_axes(): i = nditer([a, a.T], [], [['readonly']] * 2, op_axes=[[0, 1], [1, 0]]) assert_(all([x == y for (x, y) in i])) a = arange(24).reshape(2, 3, 4) - i = nditer([a.T, a], [], [['readonly']] * 2, op_axes=[[2, 1, 0], None]) + i = nditer([a.transpose(), a], [], [['readonly']] * 2, + op_axes=[[2, 1, 0], None]) assert_(all([x == y for (x, y) in i])) # Broadcast 1D to any dimension @@ -1532,7 +1533,7 @@ def test_iter_allocate_output_itorder(): assert_equal(i.operands[1].strides, a.strides) assert_equal(i.operands[1].dtype, np.dtype('f4')) # F-order input, best iteration order - a = arange(24, dtype='i4').reshape(2, 3, 4).T + a = arange(24, dtype='i4').reshape(2, 3, 4).transpose() i = nditer([a, None], [], [['readonly'], ['writeonly', 'allocate']], op_dtypes=[None, np.dtype('f4')]) assert_equal(i.operands[1].shape, a.shape) @@ -1796,7 +1797,7 @@ def test_iter_buffering(): # Test buffering with several buffer sizes and types arrays = [] # F-order swapped array - _tmp = np.arange(24, dtype='c16').reshape(2, 3, 4).T + _tmp = np.arange(24, dtype='c16').reshape(2, 3, 4).transpose() _tmp = _tmp.view(_tmp.dtype.newbyteorder()).byteswap() arrays.append(_tmp) # Contiguous 1-dimensional array @@ -1807,7 +1808,7 @@ def test_iter_buffering(): a[:] = np.arange(16, dtype='i4') arrays.append(a) # 4-D F-order array - arrays.append(np.arange(120, dtype='i4').reshape(5, 3, 2, 4).T) + arrays.append(np.arange(120, dtype='i4').reshape(5, 3, 2, 4).transpose()) for a in arrays: for buffersize in (1, 2, 3, 5, 8, 11, 16, 1024): vals = [] @@ -1826,7 +1827,7 @@ def test_iter_write_buffering(): # Test that buffering of writes is working # F-order swapped array - a = np.arange(24).reshape(2, 3, 4).T + a = np.arange(24).reshape(2, 3, 4).transpose() a = a.view(a.dtype.newbyteorder()).byteswap() i = nditer(a, ['buffered'], [['readwrite', 'nbo', 'aligned']], diff --git a/numpy/_core/tests/test_regression.py b/numpy/_core/tests/test_regression.py index fbfa9311a1dc..98907447e093 100644 --- a/numpy/_core/tests/test_regression.py +++ b/numpy/_core/tests/test_regression.py @@ -887,7 +887,7 @@ def test_string_sort_with_zeros(self): def test_copy_detection_zero_dim(self): # Ticket #658 - np.indices((0, 3, 4)).T.reshape(-1, 3) + np.indices((0, 3, 4)).transpose().reshape(-1, 3) def test_flat_byteorder(self): # Ticket #657 @@ -906,7 +906,7 @@ def test_flat_index_byteswap(self): def test_copy_detection_corner_case(self): # Ticket #658 - np.indices((0, 3, 4)).T.reshape(-1, 3) + np.indices((0, 3, 4)).transpose().reshape(-1, 3) def test_object_array_refcounting(self): # Ticket #633 diff --git a/numpy/_core/tests/test_shape_base.py b/numpy/_core/tests/test_shape_base.py index f7b944be08b7..161de934d04b 100644 --- a/numpy/_core/tests/test_shape_base.py +++ b/numpy/_core/tests/test_shape_base.py @@ -358,7 +358,10 @@ def test_concatenate(self): a2 = res[..., 6:] assert_array_equal(concatenate((a0, a1, a2), 2), res) assert_array_equal(concatenate((a0, a1, a2), -1), res) - assert_array_equal(concatenate((a0.T, a1.T, a2.T), 0), res.T) + assert_array_equal( + concatenate((a0.transpose(), a1.transpose(), a2.transpose()), 0), + res.transpose(), + ) out = res.copy() rout = concatenate((a0, a1, a2), 2, out=out) diff --git a/numpy/_core/tests/test_ufunc.py b/numpy/_core/tests/test_ufunc.py index f2b3f5a35a37..44cc4d83e124 100644 --- a/numpy/_core/tests/test_ufunc.py +++ b/numpy/_core/tests/test_ufunc.py @@ -1037,8 +1037,8 @@ def test_incontiguous_array(self): assert_equal(x[0, 0, 0, 0, 0, 0], -1, err_msg=msg2) assert_array_equal(np.vecdot(a, b), np.sum(a * b, axis=-1), err_msg=msg) x = np.arange(24).reshape(2, 3, 4) - a = x.T - b = x.T + a = x.transpose() + b = x.transpose() a[0, 0, 0] = -1 assert_equal(x[0, 0, 0], -1, err_msg=msg2) assert_array_equal(np.vecdot(a, b), np.sum(a * b, axis=-1), err_msg=msg) diff --git a/numpy/fft/tests/test_pocketfft.py b/numpy/fft/tests/test_pocketfft.py index 021181845b3b..00b197f8ee0a 100644 --- a/numpy/fft/tests/test_pocketfft.py +++ b/numpy/fft/tests/test_pocketfft.py @@ -413,7 +413,7 @@ def test_all_1d_norm_preserving(self): def test_fftn_out_argument(self, dtype, transpose, axes): def zeros_like(x): if transpose: - return np.zeros_like(x.T).T + return np.zeros_like(x.transpose()).transpose() else: return np.zeros_like(x) diff --git a/numpy/lib/_function_base_impl.py b/numpy/lib/_function_base_impl.py index 63346088b6e2..d3622850f5ca 100644 --- a/numpy/lib/_function_base_impl.py +++ b/numpy/lib/_function_base_impl.py @@ -2771,7 +2771,7 @@ def cov(m, y=None, rowvar=True, bias=False, ddof=None, fweights=None, >>> v1 = np.sum(w) >>> v2 = np.sum(w * a) >>> m -= np.sum(m * w, axis=None, keepdims=True) / v1 - >>> cov = np.dot(m * w, m.T) * v1 / (v1**2 - ddof * v2) + >>> cov = np.dot(m * w, m) * v1 / (v1**2 - ddof * v2) Note that when ``a == 1``, the normalization factor ``v1 / (v1**2 - ddof * v2)`` goes over to ``1 / (np.sum(f) - ddof)`` diff --git a/numpy/lib/_polynomial_impl.py b/numpy/lib/_polynomial_impl.py index a58ca76ec2b0..f4a8eae989d4 100644 --- a/numpy/lib/_polynomial_impl.py +++ b/numpy/lib/_polynomial_impl.py @@ -673,7 +673,7 @@ def polyfit(x, y, deg, rcond=None, full=False, w=None, cov=False): scale = NX.sqrt((lhs * lhs).sum(axis=0)) lhs /= scale c, resids, rank, s = lstsq(lhs, rhs, rcond) - c = (c.T / scale).T # broadcast scale coefficients + c = (c.transpose() / scale).transpose() # broadcast scale coefficients # warn on rank reduction, which indicates an ill conditioned matrix if rank != order and not full: diff --git a/numpy/lib/tests/test_stride_tricks.py b/numpy/lib/tests/test_stride_tricks.py index fe40c953a147..07650245fb78 100644 --- a/numpy/lib/tests/test_stride_tricks.py +++ b/numpy/lib/tests/test_stride_tricks.py @@ -49,8 +49,8 @@ def assert_same_as_ufunc(shape0, shape1, transposed=False, flipped=False): n = int(np.multiply.reduce(shape1)) x1 = np.arange(n).reshape(shape1) if transposed: - x0 = x0.T - x1 = x1.T + x0 = x0.transpose() + x1 = x1.transpose() if flipped: x0 = x0[::-1] x1 = x1[::-1] diff --git a/numpy/polynomial/polyutils.py b/numpy/polynomial/polyutils.py index 18dc0a8d1d24..439cd1f2f175 100644 --- a/numpy/polynomial/polyutils.py +++ b/numpy/polynomial/polyutils.py @@ -621,8 +621,8 @@ def _fit(vander_f, x, y, deg, rcond=None, full=False, w=None): van = vander_f(x, lmax)[:, deg] # set up the least squares matrices in transposed form - lhs = van.T - rhs = y.T + lhs = van.transpose() + rhs = y.transpose() if w is not None: w = np.asarray(w) + 0.0 if w.ndim != 1: @@ -646,8 +646,9 @@ def _fit(vander_f, x, y, deg, rcond=None, full=False, w=None): scl[scl == 0] = 1 # Solve the least squares problem. - c, resids, rank, s = np.linalg.lstsq(lhs.T / scl, rhs.T, rcond) - c = (c.T / scl).T + c, resids, rank, s = np.linalg.lstsq(lhs.transpose() / scl, + rhs.transpose(), rcond) + c = (c.transpose() / scl).transpose() # Expand c to include non-fitted coefficients which are set to zero if deg.ndim > 0: From d2e40341c7f09364126289b7984b403ff17d7e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Sat, 12 Apr 2025 19:11:04 +0000 Subject: [PATCH 2/4] Provide fix guidance --- doc/release/upcoming_changes/28678.deprecation.rst | 6 ++++-- numpy/_core/src/multiarray/getset.c | 5 ++++- numpy/_core/src/multiarray/scalartypes.c.src | 6 +++++- numpy/_core/tests/test_deprecations.py | 2 +- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/doc/release/upcoming_changes/28678.deprecation.rst b/doc/release/upcoming_changes/28678.deprecation.rst index 4b83a39ce046..6e4fc46dbf06 100644 --- a/doc/release/upcoming_changes/28678.deprecation.rst +++ b/doc/release/upcoming_changes/28678.deprecation.rst @@ -1,2 +1,4 @@ -* ``arr.T`` property has been deprecated for array scalars and arrays with - dimensionality different than ``2``. +* ``arr.T`` property has been deprecated for array scalars and arrays with dimensionality + different than ``2`` to be compatible with the Array API standard. To achieve the same + behavior when ``arr.ndim != 2``, either ``np.permute_dims(arr, range(arr.ndim)[::-1])`` + (also compatible with the Array API) or ``arr.transpose()`` can be used. diff --git a/numpy/_core/src/multiarray/getset.c b/numpy/_core/src/multiarray/getset.c index 9b2c3384bb0a..c05dd415929f 100644 --- a/numpy/_core/src/multiarray/getset.c +++ b/numpy/_core/src/multiarray/getset.c @@ -852,7 +852,10 @@ array_transpose_get(PyArrayObject *self, void *NPY_UNUSED(ignored)) if (ndim != 2) { if (PyErr_WarnFormat(PyExc_UserWarning, 1, "In the future `.T` property will be supported for " - "2-dim arrays only. Here it is %d-dim array.", + "2-dim arrays only. Received %d-dim array. Either " + "`np.permute_dims(arr, range(arr.ndim)[::-1])` " + "(compatible with the Array API) or `arr.transpose()` " + "should be used instead.", ndim) < 0) { return NULL; } diff --git a/numpy/_core/src/multiarray/scalartypes.c.src b/numpy/_core/src/multiarray/scalartypes.c.src index b3e35ffdc685..662190a5142a 100644 --- a/numpy/_core/src/multiarray/scalartypes.c.src +++ b/numpy/_core/src/multiarray/scalartypes.c.src @@ -1927,7 +1927,11 @@ gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored)) { if (PyErr_WarnEx(PyExc_UserWarning, "In the future `.T` property for array scalars will " - "raise an error.", 1) < 0) { + "raise an error. If you call `.T` on an array scalar " + "intentionally you can safely drop it. In other cases " + "`np.permute_dims(arr, range(arr.ndim)[::-1])` " + "(compatible with the Array API) or `arr.transpose()` " + "should be used instead.", 1) < 0) { return NULL; } Py_INCREF(self); diff --git a/numpy/_core/tests/test_deprecations.py b/numpy/_core/tests/test_deprecations.py index 41791149ba1e..d884511f844b 100644 --- a/numpy/_core/tests/test_deprecations.py +++ b/numpy/_core/tests/test_deprecations.py @@ -464,5 +464,5 @@ def test_deprecated_T_non_2dim(): UserWarning, match="In the future `.T` property will be " "supported for 2-dim arrays only. " - f"Here it is {len(shape)}-dim array."): + f"Received {len(shape)}-dim array."): np.ones(shape).T From 0892ed39b554c6963abc3767578e608b18ba7c43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Sat, 19 Apr 2025 14:35:10 +0000 Subject: [PATCH 3/4] Apply review comments --- .../upcoming_changes/28678.deprecation.rst | 7 +-- numpy/_core/src/multiarray/getset.c | 10 ++-- numpy/_core/src/multiarray/scalartypes.c.src | 9 ++-- numpy/_core/tests/test_deprecations.py | 49 ++++++++++++++----- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/doc/release/upcoming_changes/28678.deprecation.rst b/doc/release/upcoming_changes/28678.deprecation.rst index 6e4fc46dbf06..99155672bcae 100644 --- a/doc/release/upcoming_changes/28678.deprecation.rst +++ b/doc/release/upcoming_changes/28678.deprecation.rst @@ -1,4 +1,5 @@ * ``arr.T`` property has been deprecated for array scalars and arrays with dimensionality - different than ``2`` to be compatible with the Array API standard. To achieve the same - behavior when ``arr.ndim != 2``, either ``np.permute_dims(arr, range(arr.ndim)[::-1])`` - (also compatible with the Array API) or ``arr.transpose()`` can be used. + different than ``2`` to be compatible with the Array API standard. To achieve similar + behavior when ``arr.ndim != 2``, either ``arr.transpose()``, or ``arr.mT`` (swaps + the last two axes only), or ``np.permute_dims(arr, range(arr.ndim)[::-1])`` (compatible + with the Array API) can be used. diff --git a/numpy/_core/src/multiarray/getset.c b/numpy/_core/src/multiarray/getset.c index c05dd415929f..de39ce8f7b2d 100644 --- a/numpy/_core/src/multiarray/getset.c +++ b/numpy/_core/src/multiarray/getset.c @@ -850,13 +850,13 @@ array_transpose_get(PyArrayObject *self, void *NPY_UNUSED(ignored)) { int ndim = PyArray_NDIM(self); if (ndim != 2) { - if (PyErr_WarnFormat(PyExc_UserWarning, 1, + /* Deprecated 2025-04-19, NumPy 2.3 */ + if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, "In the future `.T` property will be supported for " "2-dim arrays only. Received %d-dim array. Either " - "`np.permute_dims(arr, range(arr.ndim)[::-1])` " - "(compatible with the Array API) or `arr.transpose()` " - "should be used instead.", - ndim) < 0) { + "`arr.transpose()` or `.mT` (which swaps the last " + "two axes only) should be used instead." + "(Deprecated NumPy 2.3)", ndim) < 0) { return NULL; } } diff --git a/numpy/_core/src/multiarray/scalartypes.c.src b/numpy/_core/src/multiarray/scalartypes.c.src index 662190a5142a..9560ae200cd3 100644 --- a/numpy/_core/src/multiarray/scalartypes.c.src +++ b/numpy/_core/src/multiarray/scalartypes.c.src @@ -1925,13 +1925,14 @@ gentype_flat_get(PyObject *self, void *NPY_UNUSED(ignored)) static PyObject * gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored)) { - if (PyErr_WarnEx(PyExc_UserWarning, + /* Deprecated 2025-04-19, NumPy 2.3 */ + if (DEPRECATE( "In the future `.T` property for array scalars will " "raise an error. If you call `.T` on an array scalar " "intentionally you can safely drop it. In other cases " - "`np.permute_dims(arr, range(arr.ndim)[::-1])` " - "(compatible with the Array API) or `arr.transpose()` " - "should be used instead.", 1) < 0) { + "`arr.transpose()` or `.mT` (which swaps the last " + "two axes only) should be used instead. " + "(Deprecated NumPy 2.3)") < 0) { return NULL; } Py_INCREF(self); diff --git a/numpy/_core/tests/test_deprecations.py b/numpy/_core/tests/test_deprecations.py index d884511f844b..8cc802e130bd 100644 --- a/numpy/_core/tests/test_deprecations.py +++ b/numpy/_core/tests/test_deprecations.py @@ -43,7 +43,8 @@ def setup_method(self): def teardown_method(self): self.warn_ctx.__exit__() - def assert_deprecated(self, function, num=1, ignore_others=False, + def assert_deprecated(self, function, num=1, msg_patterns=None, + ignore_others=False, function_fails=False, exceptions=np._NoValue, args=(), kwargs={}): @@ -61,6 +62,11 @@ def assert_deprecated(self, function, num=1, ignore_others=False, The function to test num : int Number of DeprecationWarnings to expect. This should normally be 1. + msg_patterns : str or tuple of str + Patterns for which warning messages should match. For `str` each + warning should match to the same pattern. For a tuple of `str` + each warning should match against the corresponding pattern. + For `None` this check is skipped. ignore_others : bool Whether warnings of the wrong type should be ignored (note that the message is not checked) @@ -94,6 +100,14 @@ def assert_deprecated(self, function, num=1, ignore_others=False, # just in case, clear the registry num_found = 0 for warning in self.log: + if msg_patterns is not None: + pattern = (msg_patterns if isinstance(msg_patterns, str) else + msg_patterns[num_found]) + msg = warning.message.args[0] + if re.match(pattern, msg) is None: + raise AssertionError( + "expected %s warning message pattern but got: %s" % + (pattern, msg)) if warning.category is self.warning_cls: num_found += 1 elif not ignore_others: @@ -143,9 +157,17 @@ def test_assert_deprecated(self): lambda: None) def foo(): + warnings.warn("foo bar", category=DeprecationWarning, + stacklevel=2) + + def foo_many(): warnings.warn("foo", category=DeprecationWarning, stacklevel=2) + warnings.warn("bar", category=DeprecationWarning, stacklevel=2) test_case_instance.assert_deprecated(foo) + test_case_instance.assert_deprecated(foo, msg_patterns="foo") + test_case_instance.assert_deprecated(foo_many, num=2, + msg_patterns=("foo", "^bar$")) test_case_instance.teardown_method() @@ -454,15 +476,18 @@ def test_deprecated(self): ) -def test_deprecated_T_non_2dim(): +class TestDeprecatedTNon2Dim(_DeprecationTestCase): # Deprecated in Numpy 2.3, 2025-04 - with pytest.warns(UserWarning, match="In the future `.T` property for " - "array scalars will raise an error."): - np.int64(1).T - for shape in [(5,), (2, 3, 4)]: - with pytest.warns( - UserWarning, - match="In the future `.T` property will be " - "supported for 2-dim arrays only. " - f"Received {len(shape)}-dim array."): - np.ones(shape).T + def test_deprecated(self): + self.assert_deprecated( + lambda: np.int64(1).T, + msg_patterns="In the future `.T` property for " + "array scalars will raise an error." + ) + for shape in [(5,), (2, 3, 4)]: + self.assert_deprecated( + lambda: np.ones(shape).T, + msg_patterns="In the future `.T` property will be " + "supported for 2-dim arrays only. " + f"Received {len(shape)}-dim array." + ) From 13b5692a2df80fe8e7568340b07d2bc58c87fc45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Sok=C3=B3=C5=82?= Date: Tue, 13 May 2025 16:12:05 +0000 Subject: [PATCH 4/4] Apply review comments --- .../upcoming_changes/28678.deprecation.rst | 2 +- numpy/_core/src/multiarray/getset.c | 8 +-- numpy/_core/src/multiarray/scalartypes.c.src | 6 +- numpy/_core/tests/test_deprecations.py | 58 +++++-------------- 4 files changed, 24 insertions(+), 50 deletions(-) diff --git a/doc/release/upcoming_changes/28678.deprecation.rst b/doc/release/upcoming_changes/28678.deprecation.rst index 99155672bcae..2e8e08346399 100644 --- a/doc/release/upcoming_changes/28678.deprecation.rst +++ b/doc/release/upcoming_changes/28678.deprecation.rst @@ -1,4 +1,4 @@ -* ``arr.T`` property has been deprecated for array scalars and arrays with dimensionality +* The ``arr.T`` property has been deprecated for array scalars and arrays with dimensionality different than ``2`` to be compatible with the Array API standard. To achieve similar behavior when ``arr.ndim != 2``, either ``arr.transpose()``, or ``arr.mT`` (swaps the last two axes only), or ``np.permute_dims(arr, range(arr.ndim)[::-1])`` (compatible diff --git a/numpy/_core/src/multiarray/getset.c b/numpy/_core/src/multiarray/getset.c index de39ce8f7b2d..d55f610feb18 100644 --- a/numpy/_core/src/multiarray/getset.c +++ b/numpy/_core/src/multiarray/getset.c @@ -852,10 +852,10 @@ array_transpose_get(PyArrayObject *self, void *NPY_UNUSED(ignored)) if (ndim != 2) { /* Deprecated 2025-04-19, NumPy 2.3 */ if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1, - "In the future `.T` property will be supported for " - "2-dim arrays only. Received %d-dim array. Either " - "`arr.transpose()` or `.mT` (which swaps the last " - "two axes only) should be used instead." + "In the future, the `.T` property will be supported for " + "2-dimensional arrays only. Received %d-dimensional " + "array. Either `arr.transpose()` or `.mT` (which swaps " + "the last two axes only) should be used instead." "(Deprecated NumPy 2.3)", ndim) < 0) { return NULL; } diff --git a/numpy/_core/src/multiarray/scalartypes.c.src b/numpy/_core/src/multiarray/scalartypes.c.src index 9560ae200cd3..4fb708a0db38 100644 --- a/numpy/_core/src/multiarray/scalartypes.c.src +++ b/numpy/_core/src/multiarray/scalartypes.c.src @@ -1927,9 +1927,9 @@ gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored)) { /* Deprecated 2025-04-19, NumPy 2.3 */ if (DEPRECATE( - "In the future `.T` property for array scalars will " - "raise an error. If you call `.T` on an array scalar " - "intentionally you can safely drop it. In other cases " + "In the future, the `.T` property for array scalars will " + "raise an error. If you called `.T` on an array scalar " + "intentionally, you can safely drop it. In other cases, " "`arr.transpose()` or `.mT` (which swaps the last " "two axes only) should be used instead. " "(Deprecated NumPy 2.3)") < 0) { diff --git a/numpy/_core/tests/test_deprecations.py b/numpy/_core/tests/test_deprecations.py index 8cc802e130bd..0720b4682387 100644 --- a/numpy/_core/tests/test_deprecations.py +++ b/numpy/_core/tests/test_deprecations.py @@ -13,12 +13,6 @@ import numpy as np from numpy.testing import assert_raises, temppath -try: - import pytz # noqa: F401 - _has_pytz = True -except ImportError: - _has_pytz = False - class _DeprecationTestCase: # Just as warning: warnings uses re.match, so the start of this message @@ -43,8 +37,7 @@ def setup_method(self): def teardown_method(self): self.warn_ctx.__exit__() - def assert_deprecated(self, function, num=1, msg_patterns=None, - ignore_others=False, + def assert_deprecated(self, function, num=1, ignore_others=False, function_fails=False, exceptions=np._NoValue, args=(), kwargs={}): @@ -62,11 +55,6 @@ def assert_deprecated(self, function, num=1, msg_patterns=None, The function to test num : int Number of DeprecationWarnings to expect. This should normally be 1. - msg_patterns : str or tuple of str - Patterns for which warning messages should match. For `str` each - warning should match to the same pattern. For a tuple of `str` - each warning should match against the corresponding pattern. - For `None` this check is skipped. ignore_others : bool Whether warnings of the wrong type should be ignored (note that the message is not checked) @@ -100,14 +88,6 @@ def assert_deprecated(self, function, num=1, msg_patterns=None, # just in case, clear the registry num_found = 0 for warning in self.log: - if msg_patterns is not None: - pattern = (msg_patterns if isinstance(msg_patterns, str) else - msg_patterns[num_found]) - msg = warning.message.args[0] - if re.match(pattern, msg) is None: - raise AssertionError( - "expected %s warning message pattern but got: %s" % - (pattern, msg)) if warning.category is self.warning_cls: num_found += 1 elif not ignore_others: @@ -157,17 +137,9 @@ def test_assert_deprecated(self): lambda: None) def foo(): - warnings.warn("foo bar", category=DeprecationWarning, - stacklevel=2) - - def foo_many(): warnings.warn("foo", category=DeprecationWarning, stacklevel=2) - warnings.warn("bar", category=DeprecationWarning, stacklevel=2) test_case_instance.assert_deprecated(foo) - test_case_instance.assert_deprecated(foo, msg_patterns="foo") - test_case_instance.assert_deprecated(foo_many, num=2, - msg_patterns=("foo", "^bar$")) test_case_instance.teardown_method() @@ -476,18 +448,20 @@ def test_deprecated(self): ) -class TestDeprecatedTNon2Dim(_DeprecationTestCase): - # Deprecated in Numpy 2.3, 2025-04 +class TestDeprecatedTPropScalar(_DeprecationTestCase): + # Deprecated in Numpy 2.3, 2025-05 + message = ("In the future, the `.T` property for array scalars will " + "raise an error.") + + def test_deprecated(self): + self.assert_deprecated(lambda: np.int64(1).T) + + +class TestDeprecatedTPropNon2Dim(_DeprecationTestCase): + # Deprecated in Numpy 2.3, 2025-05 + message = ("In the future, the `.T` property will be supported for " + r"2-dimensional arrays only. Received \d+-dimensional array.") + def test_deprecated(self): - self.assert_deprecated( - lambda: np.int64(1).T, - msg_patterns="In the future `.T` property for " - "array scalars will raise an error." - ) for shape in [(5,), (2, 3, 4)]: - self.assert_deprecated( - lambda: np.ones(shape).T, - msg_patterns="In the future `.T` property will be " - "supported for 2-dim arrays only. " - f"Received {len(shape)}-dim array." - ) + self.assert_deprecated(lambda: np.ones(shape).T)