https://github.com/python/cpython/commit/bd8b2fe2b2336d85017fb9195a523ab6d32c3d56
commit: bd8b2fe2b2336d85017fb9195a523ab6d32c3d56
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-08-03T22:06:00+03:00
summary:
gh-78959: Add order parameter to memoryview.cast() (GH-153663)
memoryview.cast() now accepts a keyword-only order parameter. With
order='F' it returns a zero-copy Fortran-contiguous (column-major) view
of a flat buffer, mirroring the order argument of memoryview.tobytes().
Only 'C' (the default) and 'F' are accepted; the buffer is not copied.
files:
A
Misc/NEWS.d/next/Core_and_Builtins/2026-07-13-13-05-00.gh-issue-78959.Xy4a2B.rst
M Doc/library/stdtypes.rst
M Doc/whatsnew/3.16.rst
M Lib/test/test_memoryview.py
M Objects/clinic/memoryobject.c.h
M Objects/memoryobject.c
diff --git a/Doc/library/stdtypes.rst b/Doc/library/stdtypes.rst
index 0b6a1b488fb64f9..2c11d0edbc2cbfd 100644
--- a/Doc/library/stdtypes.rst
+++ b/Doc/library/stdtypes.rst
@@ -4871,7 +4871,7 @@ copying.
.. versionadded:: 3.2
.. method:: cast(format, /)
- cast(format, shape, /)
+ cast(format, shape, /, *, order='C')
Cast a memoryview to a new format or shape. *shape* defaults to
``[byte_length//new_itemsize]``, which means that the result view
@@ -4880,6 +4880,12 @@ copying.
1D -> C-:term:`contiguous`, C-contiguous -> 1D, and
F-contiguous -> 1D.
+ With a multidimensional *shape*, *order* selects the memory layout of
+ the result: ``'C'`` for C-contiguous (row-major, the default) or ``'F'``
+ for Fortran-contiguous (column-major). The buffer is still not copied,
+ so ``order='F'`` gives a zero-copy view over a buffer holding
+ column-major data.
+
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
@@ -5031,8 +5037,20 @@ copying.
>>> y.nbytes
96
+ Interpret a flat buffer as a Fortran-contiguous (column-major) array::
+
+ >>> buf = bytes(range(6))
+ >>> y = memoryview(buf).cast('B', shape=[3, 2], order='F')
+ >>> y.f_contiguous
+ True
+ >>> y.tolist()
+ [[0, 3], [1, 4], [2, 5]]
+
.. versionadded:: 3.3
+ .. versionchanged:: next
+ Added the *order* parameter.
+
.. attribute:: readonly
A bool indicating whether the memory is read only.
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index c607e3c620572fe..90ab6758a264cd8 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -79,6 +79,12 @@ Other language changes
F-contiguous view to a one-dimensional view.
(Contributed by Jaemin Park in :gh:`91484`.)
+* :meth:`memoryview.cast` now accepts an *order* parameter. With
+ ``order='F'`` it returns a zero-copy Fortran-contiguous (column-major)
+ view of a flat buffer, which is useful for interfacing with column-major
+ libraries.
+ (Contributed by Serhiy Storchaka in :gh:`78959`.)
+
* :ref:`Frame objects <frame-objects>` now support :mod:`weak references
<weakref>`. This allows associating extra data with active frames,
for example in debuggers, without keeping the frames (and everything
diff --git a/Lib/test/test_memoryview.py b/Lib/test/test_memoryview.py
index df5ab9f2c879ca3..3cb8a104faee5e6 100644
--- a/Lib/test/test_memoryview.py
+++ b/Lib/test/test_memoryview.py
@@ -689,6 +689,54 @@ class ArrayMemorySliceSliceTest(unittest.TestCase,
class OtherTest(unittest.TestCase):
+ def test_cast_order(self):
+ # gh-78959: cast(order=...) selects the result memory layout.
+ b = bytearray(range(6))
+ mv = memoryview(b)
+
+ c = mv.cast('B', shape=(3, 2)) # default: C-contiguous
+ self.assertTrue(c.c_contiguous)
+ self.assertFalse(c.f_contiguous)
+ self.assertEqual(c.strides, (2, 1))
+ self.assertEqual(c.tolist(), [[0, 1], [2, 3], [4, 5]])
+
+ f = mv.cast('B', shape=(3, 2), order='F') # Fortran-contiguous
+ self.assertTrue(f.f_contiguous)
+ self.assertFalse(f.c_contiguous)
+ self.assertEqual(f.strides, (1, 3))
+ self.assertEqual(f.tolist(), [[0, 3], [1, 4], [2, 5]])
+ # zero-copy view: the flat physical bytes are unchanged
+ self.assertEqual(f.tobytes('A'), bytes(range(6)))
+
+ # explicit 'C' is the default
+ self.assertTrue(mv.cast('B', shape=(3, 2), order='C').c_contiguous)
+
+ # a wider format works too (strides scale by itemsize)
+ m = memoryview(struct.pack('6i', *range(6))).cast('i', shape=(3, 2),
+ order='F')
+ self.assertTrue(m.f_contiguous)
+ self.assertEqual(m.strides, (4, 12))
+ self.assertEqual(m.tolist(), [[0, 3], [1, 4], [2, 5]])
+
+ # more than two dimensions
+ m3 = memoryview(bytearray(range(24))).cast('B', shape=(2, 3, 4),
+ order='F')
+ self.assertTrue(m3.f_contiguous)
+ self.assertEqual(m3.strides, (1, 2, 6))
+
+ # order is keyword-only and only the strings 'C'/'F' are accepted
+ self.assertRaises(TypeError, mv.cast, 'B', (3, 2), 'F')
+ self.assertRaises(TypeError, mv.cast, 'B', shape=(3, 2), order=None)
+ self.assertRaises(ValueError, mv.cast, 'B', shape=(3, 2), order='X')
+ self.assertRaises(ValueError, mv.cast, 'B', shape=(3, 2), order='A')
+
+ def test_cast_order_writable(self):
+ # An F-contiguous cast shares memory with the original buffer.
+ b = bytearray(6)
+ f = memoryview(b).cast('B', shape=(3, 2), order='F')
+ f[0, 1] = 9 # column-major: physical offset 3
+ self.assertEqual(b[3], 9)
+
def test_ctypes_cast(self):
# Issue 15944: Allow all source formats when casting to bytes.
ctypes = import_helper.import_module("ctypes")
diff --git
a/Misc/NEWS.d/next/Core_and_Builtins/2026-07-13-13-05-00.gh-issue-78959.Xy4a2B.rst
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-13-13-05-00.gh-issue-78959.Xy4a2B.rst
new file mode 100644
index 000000000000000..c13ae1430b6f1ae
--- /dev/null
+++
b/Misc/NEWS.d/next/Core_and_Builtins/2026-07-13-13-05-00.gh-issue-78959.Xy4a2B.rst
@@ -0,0 +1,4 @@
+Add the *order* parameter to :meth:`memoryview.cast`.
+With ``order='F'`` it creates a zero-copy Fortran-contiguous (column-major)
+view of a flat buffer, mirroring the *order* argument of
+:meth:`memoryview.tobytes`.
diff --git a/Objects/clinic/memoryobject.c.h b/Objects/clinic/memoryobject.c.h
index a0cf3243edc08a0..d55e7c4ac475766 100644
--- a/Objects/clinic/memoryobject.c.h
+++ b/Objects/clinic/memoryobject.c.h
@@ -148,17 +148,21 @@ memoryview_release(PyObject *self, PyObject
*Py_UNUSED(ignored))
}
PyDoc_STRVAR(memoryview_cast__doc__,
-"cast($self, /, format, shape=<unrepresentable>)\n"
+"cast($self, /, format, shape=<unrepresentable>, *, order=\'C\')\n"
"--\n"
"\n"
-"Cast a memoryview to a new format or shape.");
+"Cast a memoryview to a new format or shape.\n"
+"\n"
+"With a multidimensional *shape*, *order* selects the result\n"
+"layout: \'C\' for C-contiguous (row-major, the default) or \'F\'\n"
+"for Fortran-contiguous (column-major).");
#define MEMORYVIEW_CAST_METHODDEF \
{"cast", _PyCFunction_CAST(memoryview_cast), METH_FASTCALL|METH_KEYWORDS,
memoryview_cast__doc__},
static PyObject *
memoryview_cast_impl(PyMemoryViewObject *self, PyObject *format,
- PyObject *shape);
+ PyObject *shape, int order);
static PyObject *
memoryview_cast(PyObject *self, PyObject *const *args, Py_ssize_t nargs,
PyObject *kwnames)
@@ -166,7 +170,7 @@ memoryview_cast(PyObject *self, PyObject *const *args,
Py_ssize_t nargs, PyObjec
PyObject *return_value = NULL;
#if defined(Py_BUILD_CORE) && !defined(Py_BUILD_CORE_MODULE)
- #define NUM_KEYWORDS 2
+ #define NUM_KEYWORDS 3
static struct {
PyGC_Head _this_is_not_used;
PyObject_VAR_HEAD
@@ -175,7 +179,7 @@ memoryview_cast(PyObject *self, PyObject *const *args,
Py_ssize_t nargs, PyObjec
} _kwtuple = {
.ob_base = PyVarObject_HEAD_INIT(&PyTuple_Type, NUM_KEYWORDS)
.ob_hash = -1,
- .ob_item = { &_Py_ID(format), &_Py_ID(shape), },
+ .ob_item = { &_Py_ID(format), &_Py_ID(shape), &_Py_ID(order), },
};
#undef NUM_KEYWORDS
#define KWTUPLE (&_kwtuple.ob_base.ob_base)
@@ -184,17 +188,18 @@ memoryview_cast(PyObject *self, PyObject *const *args,
Py_ssize_t nargs, PyObjec
# define KWTUPLE NULL
#endif // !Py_BUILD_CORE
- static const char * const _keywords[] = {"format", "shape", NULL};
+ static const char * const _keywords[] = {"format", "shape", "order", NULL};
static _PyArg_Parser _parser = {
.keywords = _keywords,
.fname = "cast",
.kwtuple = KWTUPLE,
};
#undef KWTUPLE
- PyObject *argsbuf[2];
+ PyObject *argsbuf[3];
Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) -
1;
PyObject *format;
PyObject *shape = NULL;
+ int order = 'C';
args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser,
/*minpos*/ 1, /*maxpos*/ 2, /*minkw*/ 0, /*varpos*/ 0, argsbuf);
@@ -209,9 +214,30 @@ memoryview_cast(PyObject *self, PyObject *const *args,
Py_ssize_t nargs, PyObjec
if (!noptargs) {
goto skip_optional_pos;
}
- shape = args[1];
+ if (args[1]) {
+ shape = args[1];
+ if (!--noptargs) {
+ goto skip_optional_pos;
+ }
+ }
skip_optional_pos:
- return_value = memoryview_cast_impl((PyMemoryViewObject *)self, format,
shape);
+ if (!noptargs) {
+ goto skip_optional_kwonly;
+ }
+ if (!PyUnicode_Check(args[2])) {
+ _PyArg_BadArgument("cast", "argument 'order'", "a unicode character",
args[2]);
+ goto exit;
+ }
+ if (PyUnicode_GET_LENGTH(args[2]) != 1) {
+ PyErr_Format(PyExc_TypeError,
+ "cast(): argument 'order' must be a unicode character, "
+ "not a string of length %zd",
+ PyUnicode_GET_LENGTH(args[2]));
+ goto exit;
+ }
+ order = PyUnicode_READ_CHAR(args[2], 0);
+skip_optional_kwonly:
+ return_value = memoryview_cast_impl((PyMemoryViewObject *)self, format,
shape, order);
exit:
return return_value;
@@ -506,4 +532,4 @@ memoryview_index(PyObject *self, PyObject *const *args,
Py_ssize_t nargs)
exit:
return return_value;
}
-/*[clinic end generated code: output=3abf9c80cd49229a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=17a403895f4f778c input=a9049054013a1b77]*/
diff --git a/Objects/memoryobject.c b/Objects/memoryobject.c
index f6ffa337c632a0b..0bcd0b6596be776 100644
--- a/Objects/memoryobject.c
+++ b/Objects/memoryobject.c
@@ -1400,11 +1400,12 @@ copy_shape(Py_ssize_t *shape, const PyObject *seq,
Py_ssize_t ndim,
return len;
}
-/* Cast a 1-D array to a new shape. The result array will be C-contiguous.
- If the result array does not have exactly the same byte length as the
- input array, raise ValueError. */
+/* Cast a 1-D array to a new shape. The result array will be C-contiguous
+ ('C') or Fortran-contiguous ('F') according to 'order'. If the result
+ array does not have exactly the same byte length as the input array, raise
+ TypeError. */
static int
-cast_to_ND(PyMemoryViewObject *mv, const PyObject *shape, int ndim)
+cast_to_ND(PyMemoryViewObject *mv, const PyObject *shape, int ndim, char order)
{
Py_buffer *view = &mv->view;
Py_ssize_t len;
@@ -1425,7 +1426,10 @@ cast_to_ND(PyMemoryViewObject *mv, const PyObject
*shape, int ndim)
len = copy_shape(view->shape, shape, ndim, view->itemsize);
if (len < 0)
return -1;
- init_strides_from_shape(view);
+ if (order == 'F')
+ init_fortran_strides_from_shape(view);
+ else
+ init_strides_from_shape(view);
}
if (view->len != len) {
@@ -1469,14 +1473,20 @@ memoryview.cast
format: unicode
shape: object = NULL
+ *
+ order: int(accept={str}) = 'C'
Cast a memoryview to a new format or shape.
+
+With a multidimensional *shape*, *order* selects the result
+layout: 'C' for C-contiguous (row-major, the default) or 'F'
+for Fortran-contiguous (column-major).
[clinic start generated code]*/
static PyObject *
memoryview_cast_impl(PyMemoryViewObject *self, PyObject *format,
- PyObject *shape)
-/*[clinic end generated code: output=bae520b3a389cbab input=138936cc9041b1a3]*/
+ PyObject *shape, int order)
+/*[clinic end generated code: output=6410d87141f6bb56 input=4a1a2326c59caeb3]*/
{
PyMemoryViewObject *mv = NULL;
Py_ssize_t ndim = 1;
@@ -1484,6 +1494,11 @@ memoryview_cast_impl(PyMemoryViewObject *self, PyObject
*format,
CHECK_RELEASED(self);
CHECK_RESTRICTED(self);
+ if (order != 'C' && order != 'F') {
+ PyErr_SetString(PyExc_ValueError, "order must be 'C' or 'F'");
+ return NULL;
+ }
+
if (!MV_C_CONTIGUOUS(self->flags)) {
if (shape || !MV_F_CONTIGUOUS(self->flags)) {
PyErr_SetString(PyExc_TypeError,
@@ -1519,7 +1534,7 @@ memoryview_cast_impl(PyMemoryViewObject *self, PyObject
*format,
if (cast_to_1D(mv, format) < 0)
goto error;
- if (shape && cast_to_ND(mv, shape, (int)ndim) < 0)
+ if (shape && cast_to_ND(mv, shape, (int)ndim, (char)order) < 0)
goto error;
return (PyObject *)mv;
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]