Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4867,14 +4867,20 @@ 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
will be one-dimensional. The return value is a new memoryview, but
the buffer itself is not copied. Supported casts are 1D -> C-:term:`contiguous`
and C-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
Expand Down Expand Up @@ -5022,8 +5028,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.
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.16.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ New features
Other language changes
======================

* :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`.)



New modules
Expand Down
48 changes: 48 additions & 0 deletions Lib/test/test_memoryview.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
Original file line number Diff line number Diff line change
@@ -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`.
46 changes: 36 additions & 10 deletions Objects/clinic/memoryobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

31 changes: 23 additions & 8 deletions Objects/memoryobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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) {
Expand Down Expand Up @@ -1469,21 +1473,32 @@ 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;

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)) {
PyErr_SetString(PyExc_TypeError,
"memoryview: casts are restricted to C-contiguous views");
Expand Down Expand Up @@ -1517,7 +1532,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;
Expand Down
Loading