.. 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
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
>>> 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.
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
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")
--- /dev/null
+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`.
}
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)
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
} _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)
# 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);
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;
exit:
return return_value;
}
-/*[clinic end generated code: output=3abf9c80cd49229a input=a9049054013a1b77]*/
+/*[clinic end generated code: output=17a403895f4f778c input=a9049054013a1b77]*/
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;
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) {
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)) {
if (shape || !MV_F_CONTIGUOUS(self->flags)) {
PyErr_SetString(PyExc_TypeError,
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;