Skip to content

Commit

Permalink
gh-83791: Raise TypeError for len(memoryview_0d) (#18463)
Browse files Browse the repository at this point in the history
Changes the behaviour of `len` on a zero-dimensional `memoryview` to raise `TypeError`. Previously, `len` would return `1`.
  • Loading branch information
eric-wieser committed Apr 22, 2023
1 parent caed494 commit 3d2a468
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 22 deletions.
15 changes: 9 additions & 6 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3715,12 +3715,15 @@ copying.
types such as :class:`bytes` and :class:`bytearray`, an element is a single
byte, but other types such as :class:`array.array` may have bigger elements.

``len(view)`` is equal to the length of :class:`~memoryview.tolist`.
If ``view.ndim = 0``, the length is 1. If ``view.ndim = 1``, the length
is equal to the number of elements in the view. For higher dimensions,
the length is equal to the length of the nested list representation of
the view. The :class:`~memoryview.itemsize` attribute will give you the
number of bytes in a single element.
``len(view)`` is equal to the length of :class:`~memoryview.tolist`, which
is the nested list representation of the view. If ``view.ndim = 1``,
this is equal to the number of elements in the view.

.. versionchanged:: 3.12
If ``view.ndim == 0``, ``len(view)`` now raises :exc:`TypeError` instead of returning 1.

The :class:`~memoryview.itemsize` attribute will give you the number of
bytes in a single element.

A :class:`memoryview` supports slicing and indexing to expose its data.
One-dimensional slicing will result in a subview::
Expand Down
6 changes: 4 additions & 2 deletions Lib/test/test_buffer.py
Original file line number Diff line number Diff line change
Expand Up @@ -965,8 +965,10 @@ def check_memoryview(m, expected_readonly=readonly):
self.assertEqual(m.strides, tuple(strides))
self.assertEqual(m.suboffsets, tuple(suboffsets))

n = 1 if ndim == 0 else len(lst)
self.assertEqual(len(m), n)
if ndim == 0:
self.assertRaises(TypeError, len, m)
else:
self.assertEqual(len(m), len(lst))

rep = result.tolist() if fmt else result.tobytes()
self.assertEqual(rep, lst)
Expand Down
24 changes: 11 additions & 13 deletions Lib/test/test_ctypes/test_pep3118.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def test_native_types(self):
if shape:
self.assertEqual(len(v), shape[0])
else:
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
self.assertRaises(TypeError, len, v)
self.assertEqual(v.itemsize, sizeof(itemtp))
self.assertEqual(v.shape, shape)
# XXX Issue #12851: PyCData_NewGetBuffer() must provide strides
Expand All @@ -39,11 +39,10 @@ def test_native_types(self):
# they are always read/write
self.assertFalse(v.readonly)

if v.shape:
n = 1
for dim in v.shape:
n = n * dim
self.assertEqual(n * v.itemsize, len(v.tobytes()))
n = 1
for dim in v.shape:
n = n * dim
self.assertEqual(n * v.itemsize, len(v.tobytes()))
except:
# so that we can see the failing type
print(tp)
Expand All @@ -58,7 +57,7 @@ def test_endian_types(self):
if shape:
self.assertEqual(len(v), shape[0])
else:
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob))
self.assertRaises(TypeError, len, v)
self.assertEqual(v.itemsize, sizeof(itemtp))
self.assertEqual(v.shape, shape)
# XXX Issue #12851
Expand All @@ -67,11 +66,10 @@ def test_endian_types(self):
# they are always read/write
self.assertFalse(v.readonly)

if v.shape:
n = 1
for dim in v.shape:
n = n * dim
self.assertEqual(n, len(v))
n = 1
for dim in v.shape:
n = n * dim
self.assertEqual(n * v.itemsize, len(v.tobytes()))
except:
# so that we can see the failing type
print(tp)
Expand Down Expand Up @@ -243,7 +241,7 @@ class LEPoint(LittleEndianStructure):
#
endian_types = [
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint),
(LEPoint, "T{<l:x:<l:y:}".replace('l', s_long), (), LEPoint),
(LEPoint * 1, "T{<l:x:<l:y:}".replace('l', s_long), (1,), LEPoint),
(POINTER(BEPoint), "&T{>l:x:>l:y:}".replace('l', s_long), (), POINTER(BEPoint)),
(POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
``len()`` for 0-dimensional :class:`memoryview`` objects (such as ``memoryview(ctypes.c_uint8(42))``) now raises a :exc:`TypeError`.
Previously this returned ``1``, which was not consistent with ``mem_0d[0]`` raising an :exc:`IndexError``.
6 changes: 5 additions & 1 deletion Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -2642,7 +2642,11 @@ static Py_ssize_t
memory_length(PyMemoryViewObject *self)
{
CHECK_RELEASED_INT(self);
return self->view.ndim == 0 ? 1 : self->view.shape[0];
if (self->view.ndim == 0) {
PyErr_SetString(PyExc_TypeError, "0-dim memory has no length");
return -1;
}
return self->view.shape[0];
}

/* As mapping */
Expand Down

0 comments on commit 3d2a468

Please sign in to comment.