Objects/bytearrayobject.c
cpython 3.14 @ ab2d84fe1023/Objects/bytearrayobject.c
bytearrayobject.c defines the bytearray built-in type. Unlike bytes, a bytearray is mutable: it owns a heap-allocated char * buffer (ob_val), tracks the allocated capacity in ob_alloc, and counts active buffer-protocol exports in ob_exports. Every mutation must check ob_exports first; a non-zero count means external code is reading the buffer and the mutation must be rejected. The file also supplies the full bytes-like method suite (find, replace, split, translate, decode, etc.) by sharing helper functions with bytesobject.c.
Map
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-60 | includes, _getbytevalue helper | Header; coerce an int-or-bytes argument to a single byte value | not ported |
| 61-200 | bytearray_buffer_getbuffer, bytearray_buffer_releasebuffer | Buffer-protocol slots; guard mutations via ob_exports | not ported |
| 201-360 | bytearray_resize | Grow or shrink ob_val; applies 1.125x overallocation on growth | not ported |
| 361-500 | PyByteArray_FromStringAndSize, PyByteArray_FromObject | Public constructors | not ported |
| 501-620 | PyByteArray_Size, PyByteArray_AsString, PyByteArray_Resize | Public C API | not ported |
| 621-760 | bytearray_concat, bytearray_repeat, bytearray_irepeat | Sequence slots for + and * | not ported |
| 761-880 | bytearray_getitem, bytearray_setitem, bytearray_delitem | Item and slice access/mutation | not ported |
| 881-1020 | bytearray_subscript, bytearray_ass_subscript | mp_subscript and mp_ass_subscript for extended slicing | not ported |
| 1021-1160 | bytearray_contains, bytearray_richcompare | in operator and comparison | not ported |
| 1161-1300 | bytearray_append, bytearray_extend, bytearray_insert | Mutation methods | not ported |
| 1301-1420 | bytearray_pop, bytearray_remove, bytearray_reverse | More mutation methods | not ported |
| 1421-1580 | bytearray_find, bytearray_rfind, bytearray_index, bytearray_rindex | Search methods delegating to Bytes_Find | not ported |
| 1581-1720 | bytearray_count, bytearray_startswith, bytearray_endswith | Counting and prefix/suffix tests | not ported |
| 1721-1880 | bytearray_replace, bytearray_translate | Replace and translate with optional delete set | not ported |
| 1881-2020 | bytearray_split, bytearray_rsplit, bytearray_splitlines | Split family | not ported |
| 2021-2160 | bytearray_join, bytearray_strip, bytearray_lstrip, bytearray_rstrip | Join and strip | not ported |
| 2161-2300 | bytearray_decode, bytearray_encode | Codec integration via PyUnicode_AsEncodedString | not ported |
| 2301-2400 | bytearray_repr, bytearray_iter | Repr and iterator | not ported |
| 2401-2500 | PyByteArray_Type definition | Full PyTypeObject | not ported |
Reading
Buffer protocol and export guard (lines 61 to 200)
cpython 3.14 @ ab2d84fe1023/Objects/bytearrayobject.c#L61-200
Because bytearray is mutable, any code that holds a raw pointer into ob_val through the buffer protocol must prevent concurrent mutation. CPython tracks this with ob_exports, an integer on the object itself. bytearray_buffer_getbuffer increments it; bytearray_buffer_releasebuffer decrements it. Every mutating method starts with a guard:
static int
bytearray_buffer_getbuffer(PyByteArrayObject *self,
Py_buffer *view, int flags)
{
CHECK_SIZE_T();
if (PyBuffer_FillInfo(view, (PyObject *)self,
PyByteArray_AS_STRING(self),
Py_SIZE(self), 0, flags) < 0)
return -1;
self->ob_exports++;
return 0;
}
A method like bytearray_resize calls this check before touching ob_val:
if (self->ob_exports > 0 && size != Py_SIZE(self)) {
PyErr_SetString(PyExc_BufferError,
"Existing exports of data: object cannot be re-sized");
return -1;
}
This is the same pattern that prevents a bytearray from being modified while a memoryview holds a live reference to it.
Growth policy in bytearray_resize (lines 201 to 360)
cpython 3.14 @ ab2d84fe1023/Objects/bytearrayobject.c#L201-360
bytearray_resize is the single allocation function used by every method that adds bytes. When the requested size exceeds the current allocation, it overallocates by roughly 12.5% (1.125x) to amortise the cost of repeated append calls:
static int
bytearray_resize(PyByteArrayObject *self, Py_ssize_t size)
{
Py_ssize_t alloc = self->ob_alloc;
if (size < alloc / 2) {
/* shrink aggressively only when size drops below half */
alloc = size + 1;
} else if (size < alloc) {
return 0; /* enough room already */
} else {
/* grow: add ~12.5% headroom */
alloc = size + (size >> 3) + (size < 9 ? 3 : 6);
}
sval = PyObject_Realloc(self->ob_val, alloc);
...
self->ob_val = sval;
self->ob_alloc = alloc;
Py_SET_SIZE(self, size);
self->ob_val[size] = '\0'; /* null-terminate for C interop */
return 0;
}
The null terminator at ob_val[size] is written even though bytearray is not a C string. It is there so that PyByteArray_AsString can hand the pointer to C code that expects a null-terminated buffer.
bytearray_translate and the delete set (lines 1721 to 1880)
cpython 3.14 @ ab2d84fe1023/Objects/bytearrayobject.c#L1721-1880
bytearray.translate(table, delete=b'') applies a 256-byte lookup table and simultaneously removes bytes listed in the delete set. The implementation makes a single pass, writing survivors into a result buffer:
static PyObject *
bytearray_translate(PyByteArrayObject *self, PyObject *args)
{
...
/* build a boolean delete-set from the delete argument */
if (del_obj != NULL) {
for (i = 0; i < dlen; i++)
del_table[(unsigned char)del[i]] = 1;
}
/* translate loop */
for (i = 0; i < inlen; i++) {
c = (unsigned char) input[i];
if (del_table[c])
continue; /* deleted */
if (table != NULL)
output[outlen++] = table[c];
else
output[outlen++] = c;
}
...
}
Using a 256-entry boolean array for deletion keeps the loop O(n) with no hash lookups. This mirrors the same design used in bytesobject.c and unicodeobject.c.
gopy mirror
bytearrayobject.c has no counterpart in gopy yet. The natural home for a future port is objects/bytearray.go. Go slices already carry length and capacity separately, so the 1.125x overallocation policy can be expressed with append semantics or a manual grow helper. The export-guard pattern maps to an atomic integer that mutation methods check before proceeding. The method suite can share helpers with a bytes object port, mirroring the CPython approach of reusing Bytes_Find and friends.
CPython 3.14 changes
CPython 3.14 added bytearray.resize(size) as a public method (previously only PyByteArray_Resize was available from C). The bytearray_decode path gained support for the errors='surrogatepass' handler consistently with bytes.decode. Internally, the ob_exports guard was also applied to the in-place repeat operator (*=), closing a bug where concurrent buffer users could observe a partial re-allocation.