Skip to content

Commit 3d2a468

Browse files
authored
gh-83791: Raise TypeError for len(memoryview_0d) (#18463)
Changes the behaviour of `len` on a zero-dimensional `memoryview` to raise `TypeError`. Previously, `len` would return `1`.
1 parent caed494 commit 3d2a468

File tree

5 files changed

+31
-22
lines changed

5 files changed

+31
-22
lines changed

Doc/library/stdtypes.rst

+9-6
Original file line numberDiff line numberDiff line change
@@ -3715,12 +3715,15 @@ copying.
37153715
types such as :class:`bytes` and :class:`bytearray`, an element is a single
37163716
byte, but other types such as :class:`array.array` may have bigger elements.
37173717

3718-
``len(view)`` is equal to the length of :class:`~memoryview.tolist`.
3719-
If ``view.ndim = 0``, the length is 1. If ``view.ndim = 1``, the length
3720-
is equal to the number of elements in the view. For higher dimensions,
3721-
the length is equal to the length of the nested list representation of
3722-
the view. The :class:`~memoryview.itemsize` attribute will give you the
3723-
number of bytes in a single element.
3718+
``len(view)`` is equal to the length of :class:`~memoryview.tolist`, which
3719+
is the nested list representation of the view. If ``view.ndim = 1``,
3720+
this is equal to the number of elements in the view.
3721+
3722+
.. versionchanged:: 3.12
3723+
If ``view.ndim == 0``, ``len(view)`` now raises :exc:`TypeError` instead of returning 1.
3724+
3725+
The :class:`~memoryview.itemsize` attribute will give you the number of
3726+
bytes in a single element.
37243727

37253728
A :class:`memoryview` supports slicing and indexing to expose its data.
37263729
One-dimensional slicing will result in a subview::

Lib/test/test_buffer.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -965,8 +965,10 @@ def check_memoryview(m, expected_readonly=readonly):
965965
self.assertEqual(m.strides, tuple(strides))
966966
self.assertEqual(m.suboffsets, tuple(suboffsets))
967967

968-
n = 1 if ndim == 0 else len(lst)
969-
self.assertEqual(len(m), n)
968+
if ndim == 0:
969+
self.assertRaises(TypeError, len, m)
970+
else:
971+
self.assertEqual(len(m), len(lst))
970972

971973
rep = result.tolist() if fmt else result.tobytes()
972974
self.assertEqual(rep, lst)

Lib/test/test_ctypes/test_pep3118.py

+11-13
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def test_native_types(self):
2828
if shape:
2929
self.assertEqual(len(v), shape[0])
3030
else:
31-
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
31+
self.assertRaises(TypeError, len, v)
3232
self.assertEqual(v.itemsize, sizeof(itemtp))
3333
self.assertEqual(v.shape, shape)
3434
# XXX Issue #12851: PyCData_NewGetBuffer() must provide strides
@@ -39,11 +39,10 @@ def test_native_types(self):
3939
# they are always read/write
4040
self.assertFalse(v.readonly)
4141

42-
if v.shape:
43-
n = 1
44-
for dim in v.shape:
45-
n = n * dim
46-
self.assertEqual(n * v.itemsize, len(v.tobytes()))
42+
n = 1
43+
for dim in v.shape:
44+
n = n * dim
45+
self.assertEqual(n * v.itemsize, len(v.tobytes()))
4746
except:
4847
# so that we can see the failing type
4948
print(tp)
@@ -58,7 +57,7 @@ def test_endian_types(self):
5857
if shape:
5958
self.assertEqual(len(v), shape[0])
6059
else:
61-
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
60+
self.assertRaises(TypeError, len, v)
6261
self.assertEqual(v.itemsize, sizeof(itemtp))
6362
self.assertEqual(v.shape, shape)
6463
# XXX Issue #12851
@@ -67,11 +66,10 @@ def test_endian_types(self):
6766
# they are always read/write
6867
self.assertFalse(v.readonly)
6968

70-
if v.shape:
71-
n = 1
72-
for dim in v.shape:
73-
n = n * dim
74-
self.assertEqual(n, len(v))
69+
n = 1
70+
for dim in v.shape:
71+
n = n * dim
72+
self.assertEqual(n * v.itemsize, len(v.tobytes()))
7573
except:
7674
# so that we can see the failing type
7775
print(tp)
@@ -243,7 +241,7 @@ class LEPoint(LittleEndianStructure):
243241
#
244242
endian_types = [
245243
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint),
246-
(LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), LEPoint),
244+
(LEPoint * 1, "T{<l:x:<l:y:}".replace('l', s_long), (1,), LEPoint),
247245
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
248246
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
249247
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
``len()`` for 0-dimensional :class:`memoryview`` objects (such as ``memoryview(ctypes.c_uint8(42))``) now raises a :exc:`TypeError`.
2+
Previously this returned ``1``, which was not consistent with ``mem_0d[0]`` raising an :exc:`IndexError``.

Objects/memoryobject.c

+5-1
Original file line numberDiff line numberDiff line change
@@ -2642,7 +2642,11 @@ static Py_ssize_t
26422642
memory_length(PyMemoryViewObject *self)
26432643
{
26442644
CHECK_RELEASED_INT(self);
2645-
return self->view.ndim == 0 ? 1 : self->view.shape[0];
2645+
if (self->view.ndim == 0) {
2646+
PyErr_SetString(PyExc_TypeError, "0-dim memory has no length");
2647+
return -1;
2648+
}
2649+
return self->view.shape[0];
26462650
}
26472651

26482652
/* As mapping */

0 commit comments

Comments
 (0)