Skip to content
Closed
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
7 changes: 7 additions & 0 deletions Doc/library/io.rst
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,13 @@ than raw I/O does.

.. versionadded:: 3.5

.. method:: truncate(size=None, /)

In :class:`BytesIO`, this is the same as :meth:`IOBase.truncate`.

.. versionchanged:: next
Now extends the underlying buffer as :meth:`IOBase.truncate` documents.

.. class:: BufferedReader(raw, buffer_size=DEFAULT_BUFFER_SIZE)

A buffered binary stream providing higher-level access to a readable, non
Expand Down
2 changes: 1 addition & 1 deletion Lib/_pyio.py
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,7 @@ def truncate(self, pos=None):
pos = pos_index()
if pos < 0:
raise ValueError("negative truncate position %r" % (pos,))
del self._buffer[pos:]
self._buffer.resize(pos)
return pos

def readable(self):
Expand Down
25 changes: 25 additions & 0 deletions Lib/test/test_io/test_memoryio.py
Original file line number Diff line number Diff line change
Expand Up @@ -558,6 +558,31 @@ def test_relative_seek(self):
memio.seek(1, 1)
self.assertEqual(memio.read(), buf[1:])

def test_truncate_extend(self):
# gh-71448: Extending with truncate should allocate space.
buf = self.buftype("123")
memio = self.ioclass(buf)

self.assertEqual(memio.tell(), 0)
self.assertEqual(memio.truncate(4), 4)
self.assertEqual(len(memio.getbuffer()), 4)
self.assertEqual(memio.getvalue(), b"123\x00")
self.assertEqual(memio.tell(), 0) # Truncate keeps pos.
# Truncate to position 0 should work.
self.assertEqual(memio.truncate(), 0)
self.assertEqual(memio.getvalue(), b"")

self.assertEqual(memio.seek(12), 12)
self.assertEqual(memio.truncate(1), 1)
self.assertEqual(len(memio.getbuffer()), 1)
self.assertEqual(memio.getvalue(), b"\x00")
self.assertEqual(memio.tell(), 12)

self.assertEqual(memio.truncate(), 12)
self.assertEqual(len(memio.getbuffer()), 12)
self.assertEqual(memio.getvalue(), b"\x00" * 12)
self.assertEqual(memio.tell(), 12)

def test_issue141311(self):
memio = self.ioclass()
# Seek allows PY_SSIZE_T_MAX, read should handle that.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Update :meth:`io.BytesIO.truncate` to match :meth:`io.IOBase.truncate`
resize extension behavior. All resize behavior now matches.
11 changes: 9 additions & 2 deletions Modules/_io/bytesio.c
Original file line number Diff line number Diff line change
Expand Up @@ -667,10 +667,17 @@ _io_BytesIO_truncate_impl(bytesio *self, PyObject *size)
}
}

if (new_size < self->string_size) {
if (new_size != self->string_size) {
Py_ssize_t orig_size = self->string_size;
self->string_size = new_size;
if (resize_buffer_lock_held(self, new_size) < 0)
if (resize_buffer_lock_held(self, new_size) < 0) {
return NULL;
}
/* Fill new space with zeros */
if (new_size > orig_size) {
memset(PyBytes_AS_STRING(self->buf) + orig_size, '\0',
(new_size - orig_size) * sizeof(char));
}
}

return PyLong_FromSsize_t(new_size);
Expand Down
Loading