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`.
This commit is contained in:
Eric Wieser 2023-04-22 17:32:47 +01:00 committed by GitHub
parent caed49448d
commit 3d2a46845b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 31 additions and 22 deletions

View File

@ -3715,12 +3715,15 @@ copying.
types such as :class:`bytes` and :class:`bytearray`, an element is a single 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. byte, but other types such as :class:`array.array` may have bigger elements.
``len(view)`` is equal to the length of :class:`~memoryview.tolist`. ``len(view)`` is equal to the length of :class:`~memoryview.tolist`, which
If ``view.ndim = 0``, the length is 1. If ``view.ndim = 1``, the length is the nested list representation of the view. If ``view.ndim = 1``,
is equal to the number of elements in the view. For higher dimensions, this is equal to the number of elements in the view.
the length is equal to the length of the nested list representation of
the view. The :class:`~memoryview.itemsize` attribute will give you the .. versionchanged:: 3.12
number of bytes in a single element. 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. A :class:`memoryview` supports slicing and indexing to expose its data.
One-dimensional slicing will result in a subview:: One-dimensional slicing will result in a subview::

View File

@ -965,8 +965,10 @@ class TestBufferProtocol(unittest.TestCase):
self.assertEqual(m.strides, tuple(strides)) self.assertEqual(m.strides, tuple(strides))
self.assertEqual(m.suboffsets, tuple(suboffsets)) self.assertEqual(m.suboffsets, tuple(suboffsets))
n = 1 if ndim == 0 else len(lst) if ndim == 0:
self.assertEqual(len(m), n) self.assertRaises(TypeError, len, m)
else:
self.assertEqual(len(m), len(lst))
rep = result.tolist() if fmt else result.tobytes() rep = result.tolist() if fmt else result.tobytes()
self.assertEqual(rep, lst) self.assertEqual(rep, lst)

View File

@ -28,7 +28,7 @@ class Test(unittest.TestCase):
if shape: if shape:
self.assertEqual(len(v), shape[0]) self.assertEqual(len(v), shape[0])
else: else:
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) self.assertRaises(TypeError, len, v)
self.assertEqual(v.itemsize, sizeof(itemtp)) self.assertEqual(v.itemsize, sizeof(itemtp))
self.assertEqual(v.shape, shape) self.assertEqual(v.shape, shape)
# XXX Issue #12851: PyCData_NewGetBuffer() must provide strides # XXX Issue #12851: PyCData_NewGetBuffer() must provide strides
@ -39,11 +39,10 @@ class Test(unittest.TestCase):
# they are always read/write # they are always read/write
self.assertFalse(v.readonly) self.assertFalse(v.readonly)
if v.shape: n = 1
n = 1 for dim in v.shape:
for dim in v.shape: n = n * dim
n = n * dim self.assertEqual(n * v.itemsize, len(v.tobytes()))
self.assertEqual(n * v.itemsize, len(v.tobytes()))
except: except:
# so that we can see the failing type # so that we can see the failing type
print(tp) print(tp)
@ -58,7 +57,7 @@ class Test(unittest.TestCase):
if shape: if shape:
self.assertEqual(len(v), shape[0]) self.assertEqual(len(v), shape[0])
else: else:
self.assertEqual(len(v) * sizeof(itemtp), sizeof(ob)) self.assertRaises(TypeError, len, v)
self.assertEqual(v.itemsize, sizeof(itemtp)) self.assertEqual(v.itemsize, sizeof(itemtp))
self.assertEqual(v.shape, shape) self.assertEqual(v.shape, shape)
# XXX Issue #12851 # XXX Issue #12851
@ -67,11 +66,10 @@ class Test(unittest.TestCase):
# they are always read/write # they are always read/write
self.assertFalse(v.readonly) self.assertFalse(v.readonly)
if v.shape: n = 1
n = 1 for dim in v.shape:
for dim in v.shape: n = n * dim
n = n * dim self.assertEqual(n * v.itemsize, len(v.tobytes()))
self.assertEqual(n, len(v))
except: except:
# so that we can see the failing type # so that we can see the failing type
print(tp) print(tp)
@ -243,7 +241,7 @@ class LEPoint(LittleEndianStructure):
# #
endian_types = [ endian_types = [
(BEPoint, "T{>l:x:>l:y:}".replace('l', s_long), (), BEPoint), (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(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)), (POINTER(LEPoint), "&T{<l:x:<l:y:}".replace('l', s_long), (), POINTER(LEPoint)),
] ]

View File

@ -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``.

View File

@ -2642,7 +2642,11 @@ static Py_ssize_t
memory_length(PyMemoryViewObject *self) memory_length(PyMemoryViewObject *self)
{ {
CHECK_RELEASED_INT(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 */ /* As mapping */