Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
6 changes: 5 additions & 1 deletion Doc/c-api/bytearray.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,11 @@ Direct API functions

.. c:function:: int PyByteArray_Resize(PyObject *bytearray, Py_ssize_t len)

Resize the internal buffer of *bytearray* to *len*.
Resize the internal buffer of *bytearray* to *len*. Failure is a ``-1`` return with an exception set.

.. versionchanged:: next
A negative *len* will now result in a failure.


Macros
^^^^^^
Expand Down
7 changes: 7 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -2841,6 +2841,13 @@ objects.
optional *sep* and *bytes_per_sep* parameters to insert separators
between bytes in the hex output.

.. method:: resize(size)

Resize the :class:`bytearray` to contain *size* bytes.
If :class:`bytearray` needs to grow, all new bytes will be set to null bytes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also describe the "obvious-to-us" behavior of what happens when it shrinks: the data at the end is truncated, the remaining data should be equivalent to a [:size] slice.

... and consider if the Python version should also support negative size to mean the same thing it would in a slice notation.
if (size < 0): size = max(0, len(self) + size) ?

Copy link
Contributor Author

@cmaloney cmaloney Feb 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for the truncation.

I don't like negative, because the function operates in absolute buffer size, and requesting a negative buffer size sounds like a bug I'd write and I'd prefer an exception / exit rather than debug the symptom "my buffer shrank".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my original equivalent to is wrong, which make it look like a delta, current doc one I'm validating:

if len(self) > size:
    del self[size:]
else:
    self += b'\0' * (size - len(self))


.. versionadded:: next

Since bytearray objects are sequences of integers (akin to a list), for a
bytearray object *b*, ``b[0]`` will be an integer, while ``b[0:1]`` will be
a bytearray object of length 1. (This contrasts with text strings, where
Expand Down
32 changes: 32 additions & 0 deletions Lib/test/test_bytes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1359,6 +1359,38 @@ def by(s):
b = by("Hello, world")
self.assertEqual(re.findall(br"\w+", b), [by("Hello"), by("world")])

def test_resize(self):
ba = bytearray(b'abcdef')
self.assertIsNone(ba.resize(3))
self.assertEqual(ba, bytearray(b'abc'))
self.assertIsNone(ba.resize(10))
self.assertEqual(len(ba), 10)
# Bytes beyond set values must be cleared.
self.assertEqual(ba, bytearray(b'abc\0\0\0\0\0\0\0'))
ba[3:10] = b'defghij'
self.assertEqual(ba, bytearray(b'abcdefghij'))
self.assertIsNone(ba.resize(2**20))
self.assertEqual(len(ba), 2**20)
self.assertEqual(ba, bytearray(b'abcdefghij' + b'\0' * (2 ** 20 - 10)))
self.assertIsNone(ba.resize(0))
self.assertEqual(ba, bytearray())
self.assertIsNone(ba.resize(10))
self.assertEqual(ba, bytearray(b'\0' * 10))

ba = ByteArraySubclass(b'abcdef')
self.assertIsNone(ba.resize(3))
self.assertEqual(ba, bytearray(b'abc'))

# Check arguments
self.assertRaises(TypeError, lambda: bytearray().resize())
self.assertRaises(TypeError, bytearray().resize, 10, 10)

self.assertRaises(BufferError, lambda: bytearray().resize(-1))
self.assertRaises(BufferError, lambda: bytearray().resize(-200))
self.assertRaises(MemoryError, lambda: bytearray().resize(sys.maxsize))
self.assertRaises(MemoryError, lambda: bytearray(1000).resize(sys.maxsize))


def test_setitem(self):
def setitem_as_mapping(b, i, val):
b[i] = val
Expand Down
3 changes: 2 additions & 1 deletion Lib/test/test_capi/test_bytearray.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,11 @@ def test_resize(self):
self.assertEqual(resize(ba, 3), 0)
self.assertEqual(ba, bytearray(b'abc'))

self.assertRaises(BufferError, resize, bytearray(), -1)
self.assertRaises(BufferError, resize, bytearray(), -200)
self.assertRaises(MemoryError, resize, bytearray(), PY_SSIZE_T_MAX)
self.assertRaises(MemoryError, resize, bytearray(1000), PY_SSIZE_T_MAX)

# CRASHES resize(bytearray(b'abc'), -1)
# CRASHES resize(b'abc', 0)
# CRASHES resize(object(), 0)
# CRASHES resize(NULL, 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Add :meth:`bytearray.resize` method to :class:`bytearray` wrapping
:c:func:`PyByteArray_Resize` so :class:`bytearray` can be efficiently
resized in place.
33 changes: 32 additions & 1 deletion Objects/bytearrayobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,12 @@ PyByteArray_Resize(PyObject *self, Py_ssize_t requested_size)
assert(self != NULL);
assert(PyByteArray_Check(self));
assert(logical_offset <= alloc);
assert(requested_size >= 0);

if (requested_size < 0) {
PyErr_Format(PyExc_ValueError,
"Can only resize to positive sizes, got %zd", requested_size);
return -1;
}

if (requested_size == Py_SIZE(self)) {
return 0;
Expand Down Expand Up @@ -1388,6 +1393,31 @@ bytearray_removesuffix_impl(PyByteArrayObject *self, Py_buffer *suffix)
}


/*[clinic input]
bytearray.resize
size: Py_ssize_t
New size to resize to..
/
Resize the internal buffer of bytearray to len.
[clinic start generated code]*/

static PyObject *
bytearray_resize_impl(PyByteArrayObject *self, Py_ssize_t size)
/*[clinic end generated code: output=f73524922990b2d9 input=75fd4d17c4aa47d3]*/
{
Py_ssize_t start_size = PyByteArray_GET_SIZE(self);
int result = PyByteArray_Resize((PyObject *)self, size);
if (result < 0) {
return NULL;
}
// Set new bytes to provide consistent / safer behavior in Python version.
if (size > start_size) {
memset(PyByteArray_AS_STRING(self) + start_size, 0, size - start_size);
}
Py_RETURN_NONE;
}


/*[clinic input]
bytearray.translate

Expand Down Expand Up @@ -2361,6 +2391,7 @@ static PyMethodDef bytearray_methods[] = {
BYTEARRAY_REPLACE_METHODDEF
BYTEARRAY_REMOVEPREFIX_METHODDEF
BYTEARRAY_REMOVESUFFIX_METHODDEF
BYTEARRAY_RESIZE_METHODDEF
BYTEARRAY_REVERSE_METHODDEF
BYTEARRAY_RFIND_METHODDEF
BYTEARRAY_RINDEX_METHODDEF
Expand Down
41 changes: 40 additions & 1 deletion Objects/clinic/bytearrayobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading