diff --git a/doc/python/corrade.containers.rst b/doc/python/corrade.containers.rst index 35fe203..160c980 100644 --- a/doc/python/corrade.containers.rst +++ b/doc/python/corrade.containers.rst @@ -49,10 +49,11 @@ ========================================== Unlike in C++, the view keeps a reference to the original memory owner - object, 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. + object in the `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. .. code:: pycon @@ -61,6 +62,8 @@ >>> b[1:4][:-1].owner is a True + The `owner` is :py:`None` if the view is empty. + `Comparison to Python's memoryview`_ ==================================== diff --git a/src/python/corrade/containers.cpp b/src/python/corrade/containers.cpp index 9ec77d7..8b7452b 100644 --- a/src/python/corrade/containers.cpp +++ b/src/python/corrade/containers.cpp @@ -127,7 +127,7 @@ template void arrayView(py::class_, Containers the buffer because we no longer care about the buffer descriptor -- that could allow the GC to haul away a bit more garbage */ - return Containers::pyArrayViewHolder(Containers::ArrayView{static_cast(buffer.buf), std::size_t(buffer.len)}, py::reinterpret_borrow(buffer.obj)); + return Containers::pyArrayViewHolder(Containers::ArrayView{static_cast(buffer.buf), std::size_t(buffer.len)}, buffer.len ? py::reinterpret_borrow(buffer.obj) : py::none{}); }), "Construct from a buffer") /* Length and memory owning object */ @@ -154,11 +154,13 @@ template void arrayView(py::class_, Containers /* Non-trivial stride, return a different type */ if(calculated.step != 1) { - return pyCastButNotShitty(Containers::pyArrayViewHolder(Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner)); + auto sliced = Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step); + return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor(self).owner : py::none{})); } /* Usual business */ - return pyCastButNotShitty(Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop), pyObjectHolderFor(self).owner)); + auto sliced = self.slice(calculated.start, calculated.stop); + return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor(self).owner : py::none{})); }, "Slice the view"); enableBetterBufferProtocol, arrayViewBufferProtocol>(c); @@ -340,7 +342,7 @@ template void stridedArrayView(py::class_(buffer.buf), size}, Containers::StaticArrayView{reinterpret_cast(buffer.shape)}, Containers::StaticArrayView{reinterpret_cast(buffer.strides)}}, - py::reinterpret_borrow(buffer.obj)); + buffer.len ? py::reinterpret_borrow(buffer.obj) : py::none{}); }), "Construct from a buffer") /* Length, size/stride tuple, dimension count and memory owning object */ @@ -368,7 +370,8 @@ template void stridedArrayView(py::class_& self, py::slice slice) { const Slice calculated = calculateSlice(slice, Containers::StridedDimensions{self.size()}[0]); - return Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor(self).owner); + const auto sliced = self.slice(calculated.start, calculated.stop).every(calculated.step); + return Containers::pyArrayViewHolder(sliced, calculated.start == calculated.stop ? py::none{} : pyObjectHolderFor(self).owner); }, "Slice the view"); enableBetterBufferProtocol, stridedArrayViewBufferProtocol>(c); @@ -402,14 +405,18 @@ template void stridedArrayViewND(py::class_ stops; Containers::StridedDimensions steps; + bool empty = false; for(std::size_t i = 0; i != dimensions; ++i) { const Slice calculated = calculateSlice(dimensionsTupleGet(slice, i), self.size()[i]); starts[i] = calculated.start; stops[i] = calculated.stop; steps[i] = calculated.step; + + if(calculated.start == calculated.stop) empty = true; } - return Containers::pyArrayViewHolder(self.slice(starts, stops).every(steps), pyObjectHolderFor(self).owner); + const auto sliced = self.slice(starts, stops).every(steps); + return Containers::pyArrayViewHolder(sliced, empty ? py::none{} : pyObjectHolderFor(self).owner); }, "Slice the view"); } diff --git a/src/python/corrade/test/test_containers.py b/src/python/corrade/test/test_containers.py index f55af43..b805948 100644 --- a/src/python/corrade/test/test_containers.py +++ b/src/python/corrade/test/test_containers.py @@ -66,6 +66,15 @@ class ArrayView(unittest.TestCase): del b self.assertTrue(sys.getrefcount(a), a_refcount) + def test_init_buffer_empty(self): + a = b'' + a_refcount = sys.getrefcount(a) + + b = containers.ArrayView(a) + self.assertIs(b.owner, None) + self.assertEqual(len(b), 0) + self.assertEqual(sys.getrefcount(a), a_refcount) + def test_init_buffer_memoryview_obj(self): a = b'hello' v = memoryview(a) @@ -121,10 +130,17 @@ class ArrayView(unittest.TestCase): self.assertEqual(sys.getrefcount(a), a_refcount + 1) def test_slice_empty(self): + data = b'hello' + data_refcount = sys.getrefcount(data) + # slice.start = slice.stop - a = containers.ArrayView(b'hello')[7:8] + a = containers.ArrayView(data)[7:8] self.assertEqual(len(a), 0) + # Empty view, original data not referenced at all + self.assertIs(a.owner, None) + self.assertEqual(sys.getrefcount(data), data_refcount) + def test_slice_invalid(self): with self.assertRaisesRegex(ValueError, "slice step cannot be zero"): containers.ArrayView()[::0] @@ -158,6 +174,18 @@ class ArrayView(unittest.TestCase): self.assertEqual(sys.getrefcount(b), b_refcount) self.assertEqual(sys.getrefcount(a), a_refcount + 1) + def test_slice_stride_empty(self): + data = b'hello' + data_refcount = sys.getrefcount(data) + + # slice.start = slice.stop + a = containers.ArrayView(data)[7:8:2] + self.assertEqual(len(a), 0) + + # Empty view, original data not referenced at all + self.assertIs(a.owner, None) + self.assertEqual(sys.getrefcount(data), data_refcount) + def test_slice_stride_negative(self): a = b'World_ _i_s_ _hell!' b = containers.ArrayView(a) @@ -250,6 +278,15 @@ class StridedArrayView1D(unittest.TestCase): del b self.assertTrue(sys.getrefcount(a), a_refcount) + def test_init_buffer_empty(self): + a = b'' + a_refcount = sys.getrefcount(a) + + b = containers.StridedArrayView1D(a) + self.assertIs(b.owner, None) + self.assertEqual(len(b), 0) + self.assertEqual(sys.getrefcount(a), a_refcount) + def test_init_buffer_memoryview_obj(self): a = b'hello' v = memoryview(a) @@ -311,6 +348,18 @@ class StridedArrayView1D(unittest.TestCase): self.assertEqual(sys.getrefcount(b), b_refcount) self.assertEqual(sys.getrefcount(a), a_refcount + 1) + def test_slice_empty(self): + data = b'hello' + data_refcount = sys.getrefcount(data) + + # slice.start = slice.stop + a = containers.StridedArrayView1D(data)[7:8] + self.assertEqual(a.size, (0, )) + + # Empty view, original data not referenced at all + self.assertIs(a.owner, None) + self.assertEqual(sys.getrefcount(data), data_refcount) + def test_slice_invalid(self): with self.assertRaisesRegex(TypeError, "indices must be integers"): containers.StridedArrayView1D()[-5:3:"boo"] @@ -522,6 +571,19 @@ class StridedArrayView2D(unittest.TestCase): self.assertEqual(sys.getrefcount(b), b_refcount) self.assertEqual(sys.getrefcount(a), a_refcount + 1) + def test_slice_multidimensional_empty(self): + a = memoryview(b'01234567' + b'456789ab' + b'89abcdef').cast('b', shape=[3, 8]) + a_refcount = sys.getrefcount(a) + + b = containers.StridedArrayView2D(a)[1:1,2:2] + self.assertEqual(b.size, (0, 0)) + + # Empty view, original data not referenced at all + self.assertIs(b.owner, None) + self.assertEqual(sys.getrefcount(a), a_refcount) + def test_slice_invalid(self): with self.assertRaisesRegex(ValueError, "slice step cannot be zero"): containers.StridedArrayView1D()[-5:3:0]