Browse Source

python: document and test error cases in "fancy" strided view APIs.

Changed those to be IndexError instead of ValueError, since those are
indexing dimensions. Plus added a new check for broadcasted dimension
size as otherwise it'd blow up on a C++ assert.
next
Vladimír Vondruš 3 years ago
parent
commit
4fe21deaaf
  1. 87
      doc/python/corrade.containers.rst
  2. 34
      src/python/corrade/containers.cpp
  3. 63
      src/python/corrade/test/test_containers.py

87
doc/python/corrade.containers.rst

@ -52,8 +52,8 @@
object in the :ref:`owner` field, meaning that calling :py:`del` on the
original object will *not* invalidate the view. Slicing a view creates a
new view referencing the same original object, without any dependency on
the previous view. That means a long chained slicing operation will not cause
increased memory usage.
the previous view. That means a long chained slicing operation will not
cause increased memory usage.
.. code:: pycon
@ -81,8 +81,16 @@
Provides one-dimensional read-only view on a memory range with custom
stride values. See :ref:`StridedArrayView2D`, :ref:`StridedArrayView3D`,
:ref:`MutableStridedArrayView1D` and others for multi-dimensional and
mutable equivalents.
:ref:`StridedArrayView4D`, :ref:`MutableStridedArrayView1D` and others for
multi-dimensional and mutable equivalents.
`Memory ownership and reference counting`_
==========================================
Similarly to :ref:`ArrayView`, the view keeps a reference to the original
memory owner object in the :ref:`owner` field. Slicing a view creates a
new view referencing the same original object, without any dependency on
the previous view. The :py:`owner` is :py:`None` if the view is empty.
`Comparison to Python's memoryview`_
====================================
@ -92,25 +100,96 @@
multi-dimensional slicing as well (which raises :ref:`NotImplementedError`
in Py3.7 :ref:`memoryview`).
.. py:function:: corrade.containers.StridedArrayView1D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`
.. py:function:: corrade.containers.StridedArrayView1D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`
.. py:class:: corrade.containers.MutableStridedArrayView1D
Equivalent to :ref:`StridedArrayView1D`, but implementing
:ref:`__setitem__()` as well.
.. py:function:: corrade.containers.MutableStridedArrayView1D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`
.. py:function:: corrade.containers.MutableStridedArrayView1D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`
.. py:class:: corrade.containers.StridedArrayView2D
See :ref:`StridedArrayView1D` for more information.
.. py:function:: corrade.containers.StridedArrayView2D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1`
.. py:function:: corrade.containers.StridedArrayView2D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1`
.. py:function:: corrade.containers.StridedArrayView2D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if
they're the same
.. py:class:: corrade.containers.MutableStridedArrayView2D
See :ref:`StridedArrayView1D` and :ref:`MutableStridedArrayView1D` for more
information.
.. py:function:: corrade.containers.MutableStridedArrayView2D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1`
.. py:function:: corrade.containers.MutableStridedArrayView2D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0` or :py:`1`
.. py:function:: corrade.containers.MutableStridedArrayView2D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0` or :py:`1` or if
they're the same
.. py:class:: corrade.containers.StridedArrayView3D
See :ref:`StridedArrayView1D` for more information.
.. py:function:: corrade.containers.StridedArrayView3D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2`
.. py:function:: corrade.containers.StridedArrayView3D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2`
.. py:function:: corrade.containers.StridedArrayView3D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2`
or if they're the same
.. py:class:: corrade.containers.MutableStridedArrayView3D
See :ref:`StridedArrayView1D` and :ref:`MutableStridedArrayView1D` for more
information.
.. py:function:: corrade.containers.MutableStridedArrayView3D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2`
.. py:function:: corrade.containers.MutableStridedArrayView3D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` or :py:`2`
.. py:function:: corrade.containers.MutableStridedArrayView3D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` or :py:`2`
or if they're the same
.. py:class:: corrade.containers.StridedArrayView4D
See :ref:`StridedArrayView1D` for more information.
.. py:function:: corrade.containers.StridedArrayView4D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` :py:`2` or
:py:`3`
.. py:function:: corrade.containers.StridedArrayView4D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` :py:`2` or
:py:`3`
.. py:function:: corrade.containers.StridedArrayView4D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` :py:`2` or
:py:`3` or if they're the same
.. py:class:: corrade.containers.MutableStridedArrayView4D
See :ref:`StridedArrayView1D` and :ref:`MutableStridedArrayView1D` for more
information.
.. py:function:: corrade.containers.MutableStridedArrayView4D.flipped
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` :py:`2` or
:py:`3`
.. py:function:: corrade.containers.MutableStridedArrayView4D.broadcasted
:raise IndexError: if :p:`dimension` is not :py:`0`, :py:`1` :py:`2` or
:py:`3`
.. py:function:: corrade.containers.MutableStridedArrayView4D.transposed
:raise IndexError: if :p:`a` or :p:`b` is not :py:`0`, :py:`1` :py:`2` or
:py:`3` or if they're the same

34
src/python/corrade/containers.cpp

@ -326,13 +326,18 @@ template<> struct StridedOperation<1> {
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> flipped(const View<dimensions, T>& view, unsigned dimension) {
if(dimension == 0)
return view.template flipped<0>();
PyErr_Format(PyExc_ValueError, "dimension %u out of range for a %iD view", dimension, dimensions);
PyErr_Format(PyExc_IndexError, "dimension %u out of range for a %iD view", dimension, dimensions);
throw py::error_already_set{};
}
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> broadcasted(const View<dimensions, T>& view, unsigned dimension, std::size_t size) {
if(dimension == 0)
if(dimension == 0) {
if(Containers::Size<dimensions>{view.size()}[0] != 1) {
PyErr_Format(PyExc_ValueError, "can't broadcast dimension %u with %zu elements", dimension, Containers::Size<dimensions>{view.size()}[0]);
throw py::error_already_set{};
}
return view.template broadcasted<0>(size);
PyErr_Format(PyExc_ValueError, "dimension %u out of range for a %iD view", dimension, dimensions);
}
PyErr_Format(PyExc_IndexError, "dimension %u out of range for a %iD view", dimension, dimensions);
throw py::error_already_set{};
}
};
@ -341,7 +346,7 @@ template<> struct StridedOperation<2> {
if((a == 0 && b == 1) ||
(a == 1 && b == 0))
return view.template transposed<0, 1>();
PyErr_Format(PyExc_ValueError, "dimensions %u, %u can't be transposed in a %iD view", a, b, dimensions);
PyErr_Format(PyExc_IndexError, "dimensions %u, %u can't be transposed in a %iD view", a, b, dimensions);
throw py::error_already_set{};
}
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> flipped(const View<dimensions, T>& view, unsigned dimension) {
@ -350,8 +355,13 @@ template<> struct StridedOperation<2> {
return StridedOperation<1>::flipped(view, dimension);
}
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> broadcasted(const View<dimensions, T>& view, unsigned dimension, std::size_t size) {
if(dimension == 1)
if(dimension == 1) {
if(view.size()[1] != 1) {
PyErr_Format(PyExc_ValueError, "can't broadcast dimension %u with %zu elements", dimension, view.size()[1]);
throw py::error_already_set{};
}
return view.template broadcasted<1>(size);
}
return StridedOperation<1>::broadcasted(view, dimension, size);
}
};
@ -371,8 +381,13 @@ template<> struct StridedOperation<3> {
return StridedOperation<2>::flipped(view, dimension);
}
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> broadcasted(const View<dimensions, T>& view, unsigned dimension, std::size_t size) {
if(dimension == 2)
if(dimension == 2) {
if(view.size()[2] != 1) {
PyErr_Format(PyExc_ValueError, "can't broadcast dimension %u with %zu elements", dimension, view.size()[2]);
throw py::error_already_set{};
}
return view.template broadcasted<2>(size);
}
return StridedOperation<2>::broadcasted(view, dimension, size);
}
};
@ -395,8 +410,13 @@ template<> struct StridedOperation<4> {
return StridedOperation<3>::flipped(view, dimension);
}
template<template<unsigned, class> class View, unsigned dimensions, class T> static View<dimensions, T> broadcasted(const View<dimensions, T>& view, unsigned dimension, std::size_t size) {
if(dimension == 3)
if(dimension == 3) {
if(view.size()[3] != 1) {
PyErr_Format(PyExc_ValueError, "can't broadcast dimension %u with %zu elements", dimension, view.size()[3]);
throw py::error_already_set{};
}
return view.template broadcasted<3>(size);
}
return StridedOperation<3>::broadcasted(view, dimension, size);
}
};

63
src/python/corrade/test/test_containers.py

@ -395,6 +395,30 @@ class StridedArrayView1D(unittest.TestCase):
self.assertEqual(c2.size, (6,))
self.assertEqual(c2.stride, (-2,))
def test_ops(self):
a = b'01234567'
v = memoryview(a).cast('b', shape=[8])
b = containers.StridedArrayView1D(v).flipped(0)
self.assertEqual(b.size, (8,))
self.assertEqual(b.stride, (-1,))
self.assertEqual(bytes(b), b'76543210')
d = containers.StridedArrayView1D(v)[3:4].broadcasted(0, 5)
self.assertEqual(d.size, (5,))
self.assertEqual(d.stride, (0,))
self.assertEqual(bytes(d), b'33333')
def test_ops_invalid(self):
a = b'00'
with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"):
containers.StridedArrayView1D().flipped(1)
with self.assertRaisesRegex(IndexError, "dimension 1 out of range for a 1D view"):
containers.StridedArrayView1D(a).broadcasted(1, 3)
with self.assertRaisesRegex(ValueError, "can't broadcast dimension 0 with 2 elements"):
containers.StridedArrayView1D(a).broadcasted(0, 3)
def test_convert_memoryview(self):
a = b'World is hell!'
a_refcount = sys.getrefcount(a)
@ -680,6 +704,19 @@ class StridedArrayView2D(unittest.TestCase):
self.assertEqual(d.stride, (8, 0))
self.assertEqual(bytes(d), b'3377bb')
def test_ops_invalid(self):
a = b'00'
v = memoryview(a).cast('b', shape=[1, 2])
with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"):
containers.StridedArrayView2D().flipped(2)
with self.assertRaisesRegex(IndexError, "dimension 2 out of range for a 2D view"):
containers.StridedArrayView2D(v).broadcasted(2, 3)
with self.assertRaisesRegex(ValueError, "can't broadcast dimension 1 with 2 elements"):
containers.StridedArrayView2D(v).broadcasted(1, 3)
with self.assertRaisesRegex(IndexError, "dimensions 0, 2 can't be transposed in a 2D view"):
containers.StridedArrayView2D().transposed(0, 2)
def test_convert_memoryview(self):
a = memoryview(b'01234567'
b'456789ab'
@ -777,6 +814,19 @@ class StridedArrayView3D(unittest.TestCase):
self.assertEqual(f.stride, (24, 8, 0))
self.assertEqual(bytes(f), b'000004444488888ccccc0000044444')
def test_ops_invalid(self):
a = b'00'
v = memoryview(a).cast('b', shape=[1, 1, 2])
with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"):
containers.StridedArrayView3D().flipped(3)
with self.assertRaisesRegex(IndexError, "dimension 3 out of range for a 3D view"):
containers.StridedArrayView3D(v).broadcasted(3, 3)
with self.assertRaisesRegex(ValueError, "can't broadcast dimension 2 with 2 elements"):
containers.StridedArrayView3D(v).broadcasted(2, 3)
with self.assertRaisesRegex(IndexError, "dimensions 1, 3 can't be transposed in a 3D view"):
containers.StridedArrayView3D().transposed(1, 3)
# This is just a dumb copy of the above with one dimension inserted at the
# second place.
class StridedArrayView4D(unittest.TestCase):
@ -855,6 +905,19 @@ class StridedArrayView4D(unittest.TestCase):
self.assertEqual(f.stride, (24, 24, 8, 0))
self.assertEqual(bytes(f), b'000004444488888ccccc0000044444')
def test_ops_invalid(self):
a = b'00'
v = memoryview(a).cast('b', shape=[1, 1, 1, 2])
with self.assertRaisesRegex(IndexError, "dimension 4 out of range for a 4D view"):
containers.StridedArrayView4D().flipped(4)
with self.assertRaisesRegex(IndexError, "dimension 4 out of range for a 4D view"):
containers.StridedArrayView4D(v).broadcasted(4, 3)
with self.assertRaisesRegex(ValueError, "can't broadcast dimension 3 with 2 elements"):
containers.StridedArrayView4D(v).broadcasted(3, 3)
with self.assertRaisesRegex(IndexError, "dimensions 4, 3 can't be transposed in a 4D view"):
containers.StridedArrayView4D().transposed(4, 3)
class StridedArrayViewCustomType(unittest.TestCase):
def test_short(self):
a = test_stridedarrayview.get_containers()

Loading…
Cancel
Save