bytearray
contents
related file
- cpython/Objects/bytearrayobject.c
- cpython/Include/bytearrayobject.h
- cpython/Objects/clinic/bytearrayobject.c.h
memory layout
The ob_alloc field represents the real allocated size in bytes
ob_bytes is the physical begin address, and ob_start is the logical begin address
ob_exports means how many other objects are sharing this buffer, like reference count in a way

example
empty bytearray
>>> a = bytearray(b"")
>>> id(a)
4353755656
>>> b = bytearray(b"")
>>> id(b) # they are not shared
4353755712

append
After appending a character ‘a’, ob_alloc becomes 2, and ob_bytes and ob_start both point to the same address
a.append(ord('a'))

resize
The size growth pattern is shown in the code
/* Need growing, decide on a strategy */
if (size <= alloc * 1.125) {
/* Moderate upsize; overallocate similar to list_resize() */
alloc = size + (size >> 3) + (size < 9 ? 3 : 6);
}
else {
/* Major upsize; resize up to exact size */
alloc = size + 1;
}
When appending, ob_alloc is 2 and the requested size is 2. Since 2 <= 2 * 1.125, the new allocated size is 2 + (2 » 3) + 3 ==> 5
a.append(ord('b'))

slice
b = bytearray(b"abcdefghijk")

After the slice operation, ob_start points to the real beginning of the content, and ob_bytes still points to the begin address of the malloced block
b[0:5] = [1,2]

As long as the slice operation is going to shrink the bytearray and new_size < allocate / 2 is False, the resize operation won’t shrink the actual malloced size
b[2:6] = [3, 4]

Now, in the shrink operation, new_size < allocate / 2 is True, so the resize operation will be triggered
b[0:3] = [7,8]

The growing pattern in slice operation is the same as the append operation
The requested size is 6. Since 6 < 6 * 1.125, the new allocated size is 6 + (6 » 3) + 3 ==> 9
b[0:3] = [1,2,3,4]

ob_exports
What does the field ob_exports mean? If you need details, you can refer to less-copies-in-python-with-the-buffer-protocol-and-memoryviews and PEP 3118
buf = bytearray(b"abcdefg")

The bytearray implements the buffer protocol, and memoryview is able to access the internal data block via the buffer protocol. mybuf and buf both share the same internal block.
The field ob_exports becomes 1, which indicates how many objects are currently sharing the internal block via the buffer protocol
mybuf = memoryview(buf)
mybuf[1] = 3

The same applies to the mybuf2 object (ob_exports doesn’t change because you need to call the C function defined by the buf object via the buffer protocol; mybuf2 only calls the slice function of mybuf)
mybuf2 = mybuf[:4]
mybuf2[0] = 1

ob_exports becomes 2
mybuf3 = memoryview(buf)

ob_exports becomes 0
del mybuf
del mybuf2
del mybuf3
