diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst index 1ae169350f9..f0cc56b9a51 100644 --- a/Doc/library/stdtypes.rst +++ b/Doc/library/stdtypes.rst @@ -3564,7 +3564,7 @@ copying. the buffer itself is not copied. Supported casts are 1D -> C-contiguous and C-contiguous -> 1D. - Both formats are restricted to single element native formats in + The destination format is restricted to a single element native format in :mod:`struct` syntax. One of the formats must be a byte format ('B', 'b' or 'c'). The byte length of the result must be the same as the original length. @@ -3645,6 +3645,9 @@ copying. .. versionadded:: 3.3 + .. versionchanged:: 3.5 + The source format is no longer restricted when casting to a byte view. + There are also several readonly attributes available: .. attribute:: obj diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py index 6803156be52..a6533904153 100644 --- a/Lib/test/test_buffer.py +++ b/Lib/test/test_buffer.py @@ -2559,8 +2559,7 @@ class TestBufferProtocol(unittest.TestCase): ex = ndarray(sitems, shape=[1], format=sfmt) msrc = memoryview(ex) for dfmt, _, _ in iter_format(1): - if (not is_memoryview_format(sfmt) or - not is_memoryview_format(dfmt)): + if not is_memoryview_format(dfmt): self.assertRaises(ValueError, msrc.cast, dfmt, [32//dsize]) else: @@ -2773,6 +2772,32 @@ class TestBufferProtocol(unittest.TestCase): ndim=ndim, shape=shape, strides=strides, lst=lst, cast=True) + if ctypes: + # format: "T{>l:x:>d:y:}" + class BEPoint(ctypes.BigEndianStructure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)] + point = BEPoint(100, 200.1) + m1 = memoryview(point) + m2 = m1.cast('B') + self.assertEqual(m2.obj, point) + self.assertEqual(m2.itemsize, 1) + self.assertEqual(m2.readonly, 0) + self.assertEqual(m2.ndim, 1) + self.assertEqual(m2.shape, (m2.nbytes,)) + self.assertEqual(m2.strides, (1,)) + self.assertEqual(m2.suboffsets, ()) + + x = ctypes.c_double(1.2) + m1 = memoryview(x) + m2 = m1.cast('c') + self.assertEqual(m2.obj, x) + self.assertEqual(m2.itemsize, 1) + self.assertEqual(m2.readonly, 0) + self.assertEqual(m2.ndim, 1) + self.assertEqual(m2.shape, (m2.nbytes,)) + self.assertEqual(m2.strides, (1,)) + self.assertEqual(m2.suboffsets, ()) + def test_memoryview_tolist(self): # Most tolist() tests are in self.verify() etc. diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py index 6fe61a4ecc3..da01a84f274 100644 --- a/Lib/test/test_memoryview.py +++ b/Lib/test/test_memoryview.py @@ -492,5 +492,26 @@ class ArrayMemorySliceSliceTest(unittest.TestCase, pass +class OtherTest(unittest.TestCase): + def test_ctypes_cast(self): + # Issue 15944: Allow all source formats when casting to bytes. + ctypes = test.support.import_module("ctypes") + p6 = bytes(ctypes.c_double(0.6)) + + d = ctypes.c_double() + m = memoryview(d).cast("B") + m[:2] = p6[:2] + m[2:] = p6[2:] + self.assertEqual(d.value, 0.6) + + for format in "Bbc": + with self.subTest(format): + d = ctypes.c_double() + m = memoryview(d).cast(format) + m[:2] = memoryview(p6).cast(format)[:2] + m[2:] = memoryview(p6).cast(format)[2:] + self.assertEqual(d.value, 0.6) + + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS b/Misc/NEWS index f3c9bfd81f9..9bda24a083f 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -117,6 +117,9 @@ Core and Builtins - Issue #24687: Plug refleak on SyntaxError in function parameters annotations. +- Issue #15944: memoryview: Allow arbitrary formats when casting to bytes. + Patch by Martin Panter. + Library ------- diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c index 54fd05f6f61..74cad7dc0ef 100644 --- a/Objects/memoryobject.c +++ b/Objects/memoryobject.c @@ -1197,13 +1197,6 @@ cast_to_1D(PyMemoryViewObject *mv, PyObject *format) assert(view->strides == mv->ob_array + view->ndim); assert(view->suboffsets == mv->ob_array + 2*view->ndim); - if (get_native_fmtchar(&srcchar, view->format) < 0) { - PyErr_SetString(PyExc_ValueError, - "memoryview: source format must be a native single character " - "format prefixed with an optional '@'"); - return ret; - } - asciifmt = PyUnicode_AsASCIIString(format); if (asciifmt == NULL) return ret; @@ -1216,7 +1209,8 @@ cast_to_1D(PyMemoryViewObject *mv, PyObject *format) goto out; } - if (!IS_BYTE_FORMAT(srcchar) && !IS_BYTE_FORMAT(destchar)) { + if ((get_native_fmtchar(&srcchar, view->format) < 0 || + !IS_BYTE_FORMAT(srcchar)) && !IS_BYTE_FORMAT(destchar)) { PyErr_SetString(PyExc_TypeError, "memoryview: cannot cast between two non-byte formats"); goto out;