Browse Source

python: don't keep a reference to owner for empty array views.

We don't need to reference anything from it anyway.
pull/8/head
Vladimír Vondruš 7 years ago
parent
commit
2eebb45557
  1. 11
      doc/python/corrade.containers.rst
  2. 19
      src/python/corrade/containers.cpp
  3. 64
      src/python/corrade/test/test_containers.py

11
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`_
====================================

19
src/python/corrade/containers.cpp

@ -127,7 +127,7 @@ template<class T> void arrayView(py::class_<Containers::ArrayView<T>, 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<T>{static_cast<T*>(buffer.buf), std::size_t(buffer.len)}, py::reinterpret_borrow<py::object>(buffer.obj));
return Containers::pyArrayViewHolder(Containers::ArrayView<T>{static_cast<T*>(buffer.buf), std::size_t(buffer.len)}, buffer.len ? py::reinterpret_borrow<py::object>(buffer.obj) : py::none{});
}), "Construct from a buffer")
/* Length and memory owning object */
@ -154,11 +154,13 @@ template<class T> void arrayView(py::class_<Containers::ArrayView<T>, 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<Containers::PyArrayViewHolder>(self).owner));
auto sliced = Containers::stridedArrayView(self).slice(calculated.start, calculated.stop).every(calculated.step);
return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner : py::none{}));
}
/* Usual business */
return pyCastButNotShitty(Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop), pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner));
auto sliced = self.slice(calculated.start, calculated.stop);
return pyCastButNotShitty(Containers::pyArrayViewHolder(sliced, sliced.size() ? pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner : py::none{}));
}, "Slice the view");
enableBetterBufferProtocol<Containers::ArrayView<T>, arrayViewBufferProtocol>(c);
@ -340,7 +342,7 @@ template<unsigned dimensions, class T> void stridedArrayView(py::class_<Containe
{static_cast<T*>(buffer.buf), size},
Containers::StaticArrayView<dimensions, const std::size_t>{reinterpret_cast<std::size_t*>(buffer.shape)},
Containers::StaticArrayView<dimensions, const std::ptrdiff_t>{reinterpret_cast<std::ptrdiff_t*>(buffer.strides)}},
py::reinterpret_borrow<py::object>(buffer.obj));
buffer.len ? py::reinterpret_borrow<py::object>(buffer.obj) : py::none{});
}), "Construct from a buffer")
/* Length, size/stride tuple, dimension count and memory owning object */
@ -368,7 +370,8 @@ template<unsigned dimensions, class T> void stridedArrayView(py::class_<Containe
/* Slicing of the top dimension */
.def("__getitem__", [](const Containers::StridedArrayView<dimensions, T>& self, py::slice slice) {
const Slice calculated = calculateSlice(slice, Containers::StridedDimensions<dimensions, const std::size_t>{self.size()}[0]);
return Containers::pyArrayViewHolder(self.slice(calculated.start, calculated.stop).every(calculated.step), pyObjectHolderFor<Containers::PyArrayViewHolder>(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<Containers::PyArrayViewHolder>(self).owner);
}, "Slice the view");
enableBetterBufferProtocol<Containers::StridedArrayView<dimensions, T>, stridedArrayViewBufferProtocol>(c);
@ -402,14 +405,18 @@ template<unsigned dimensions, class T> void stridedArrayViewND(py::class_<Contai
Containers::StridedDimensions<dimensions, std::size_t> stops;
Containers::StridedDimensions<dimensions, std::ptrdiff_t> steps;
bool empty = false;
for(std::size_t i = 0; i != dimensions; ++i) {
const Slice calculated = calculateSlice(dimensionsTupleGet<py::slice>(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<Containers::PyArrayViewHolder>(self).owner);
const auto sliced = self.slice(starts, stops).every(steps);
return Containers::pyArrayViewHolder(sliced, empty ? py::none{} : pyObjectHolderFor<Containers::PyArrayViewHolder>(self).owner);
}, "Slice the view");
}

64
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]

Loading…
Cancel
Save