8000 DEP: Deprecate `.T` property for non-2dim arrays and scalars by mtsokol · Pull Request #28678 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

DEP: Deprecate .T property for non-2dim arrays and scalars #28678

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions doc/release/upcoming_changes/28678.deprecation.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
* 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
with the Array API) can be used.
9 changes: 2 additions & 7 deletions numpy/_core/_add_newdocs.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
--------
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions numpy/_core/src/multiarray/getset.c
Original file line number Diff line number Diff line change
Expand Up @@ -848,6 +848,18 @@ 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) {
/* Deprecated 2025-04-19, NumPy 2.3 */
if (PyErr_WarnFormat(PyExc_DeprecationWarning, 1,
"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;
}
}
return PyArray_Transpose(self, NULL);
}

Expand Down
10 changes: 10 additions & 0 deletions numpy/_core/src/multiarray/scalartypes.c.src
Original file line number Diff line number Diff line change
Expand Up @@ -1925,6 +1925,16 @@ gentype_flat_get(PyObject *self, void *NPY_UNUSED(ignored))
static PyObject *
gentype_transpose_get(PyObject *self, void *NPY_UNUSED(ignored))
{
/* Deprecated 2025-04-19, NumPy 2.3 */
if (DEPRECATE(
"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) {
return NULL;
}
Py_INCREF(self);
return self;
}
Expand Down
25 changes: 19 additions & 6 deletions numpy/_core/tests/test_deprecations.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -452,3 +446,22 @@ def test_deprecated(self):
struct_ufunc.add_triplet, "new docs"
)
)


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):
for shape in [(5,), (2, 3, 4)]:
self.assert_deprecated(lambda: np.ones(shape).T)
20 changes: 10 additions & 10 deletions numpy/_core/tests/test_einsum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions numpy/_core/tests/test_indexing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
20 changes: 10 additions & 10 deletions numpy/_core/tests/test_multiarray.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand All @@ -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'])
Expand Down Expand Up @@ -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))
Expand Down
43 changes: 22 additions & 21 deletions numpy/_core/tests/test_nditer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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')
Expand All @@ -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')
Expand All @@ -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')
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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 = []
Expand All @@ -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']],
Expand Down
4 changes: 2 additions & 2 deletions numpy/_core/tests/test_regression.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
5 changes: 4 additions & 1 deletion numpy/_core/tests/test_shape_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading
0