https://github.com/python/cpython/commit/5ef7fa169fecf37031e4fe31b9c52ab048c9c2e5
commit: 5ef7fa169fecf37031e4fe31b9c52ab048c9c2e5
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-08-31T19:20:11+02:00
summary:
gh-155742: Get singleton in PyBytesWriter_FinishWithSize() (#155795)
If the result size of 1 byte, return the singleton rather than
creating a new bytes string.
files:
A Misc/NEWS.d/next/C_API/2026-08-14-14-47-08.gh-issue-155742.UGI3Pn.rst
M Lib/test/test_capi/test_bytes.py
M Modules/_testcapi/bytes.c
M Objects/bytesobject.c
diff --git a/Lib/test/test_capi/test_bytes.py b/Lib/test/test_capi/test_bytes.py
index d20e5016f969c2f..38cda931e7d54f3 100644
--- a/Lib/test/test_capi/test_bytes.py
+++ b/Lib/test/test_capi/test_bytes.py
@@ -299,11 +299,11 @@ def test_join(self):
bytes_join(b'', NULL)
-class BytesWriterTest(unittest.TestCase):
- result_type = bytes
+class BaseWriterTest:
+ result_type = NotImplementedError
def create_writer(self, alloc=0, string=b''):
- return _testcapi.PyBytesWriter(alloc, string, 0)
+ raise NotImplementedError
def test_create(self):
# Test PyBytesWriter_Create()
@@ -388,10 +388,48 @@ def test_example_highlevel(self):
self.assertEqual(_testcapi.byteswriter_highlevel(), b'Hello World!')
-class ByteArrayWriterTest(BytesWriterTest):
+class BytesWriterTest(BaseWriterTest, unittest.TestCase):
+ result_type = bytes
+
+ def create_writer(self, alloc=0, string=b''):
+ # Test PyBytesWriter_Create()
+ return _testcapi.PyBytesWriter(alloc, string, 0)
+
+ # Only PyBytesWriter_Create() returns singletons
+ def test_singletons(self):
+ empty = b''
+ singletons = {ch: bytes((ch,)) for ch in range(256)}
+ small_buffer = _testcapi.PyBytesWriter_small_buffer
+
+ writer = self.create_writer()
+ self.assertIs(writer.finish(), empty)
+
+ # Test writer larger than small_buffer
+ writer = self.create_writer()
+ unused_text = b'x' * (small_buffer * 2)
+ writer.write_bytes(unused_text, len(unused_text))
+ self.assertIs(writer.finish_with_size(0), empty)
+
+ for ch in range(256):
+ text = bytes((ch,))
+
+ writer = self.create_writer()
+ writer.write_bytes(text, 1)
+ self.assertIs(writer.finish(), singletons[ch])
+
+ # Test writer larger than small_buffer
+ writer = self.create_writer()
+ writer.write_bytes(text, 1)
+ unused_text = b'x' * (small_buffer * 2)
+ writer.write_bytes(unused_text, len(unused_text))
+ self.assertIs(writer.finish_with_size(1), singletons[ch])
+
+
+class ByteArrayWriterTest(BaseWriterTest, unittest.TestCase):
result_type = bytearray
def create_writer(self, alloc=0, string=b''):
+ # Test private _PyBytesWriter_CreateByteArray()
return _testcapi.PyBytesWriter(alloc, string, 1)
diff --git
a/Misc/NEWS.d/next/C_API/2026-08-14-14-47-08.gh-issue-155742.UGI3Pn.rst
b/Misc/NEWS.d/next/C_API/2026-08-14-14-47-08.gh-issue-155742.UGI3Pn.rst
new file mode 100644
index 000000000000000..8920547968a4fbd
--- /dev/null
+++ b/Misc/NEWS.d/next/C_API/2026-08-14-14-47-08.gh-issue-155742.UGI3Pn.rst
@@ -0,0 +1,2 @@
+:c:func:`PyBytesWriter_FinishWithSize` now returns single byte singletons if
+*size* equals to ``1``. Patch by Victor Stinner.
diff --git a/Modules/_testcapi/bytes.c b/Modules/_testcapi/bytes.c
index f12fc7f5f3a2a86..a868c684cc987cd 100644
--- a/Modules/_testcapi/bytes.c
+++ b/Modules/_testcapi/bytes.c
@@ -4,6 +4,8 @@
#include "parts.h"
#include "util.h"
+#include <stddef.h> // offsetof()
+
#include "pycore_bytesobject.h" // _PyBytesWriter_CreateByteArray()
@@ -150,8 +152,8 @@ writer_write_bytes(PyObject *self_raw, PyObject *args)
}
char *bytes;
- Py_ssize_t size;
- if (!PyArg_ParseTuple(args, "yn", &bytes, &size)) {
+ Py_ssize_t unused_size, size;
+ if (!PyArg_ParseTuple(args, "y#n", &bytes, &unused_size, &size)) {
return NULL;
}
@@ -377,5 +379,12 @@ _PyTestCapi_Init_Bytes(PyObject *m)
}
Py_DECREF(writer_type);
+ // PyBytesWriter.obj is the second member, small_buffer is the first member
+ long size = (long)offsetof(PyBytesWriter, obj);
+ if (PyModule_AddIntConstant(m, "PyBytesWriter_small_buffer", size) < 0) {
+ Py_DECREF(writer_type);
+ return -1;
+ }
+
return 0;
}
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index ef35dad82e8aaea..4c3da93f1019709 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3766,13 +3766,23 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer,
Py_ssize_t size)
}
}
}
+
result = writer->obj;
writer->obj = NULL;
+
+ if (size == 1 && !writer->use_bytearray) {
+ // Get the single byte singleton
+ unsigned char ch = PyBytes_AS_STRING(result)[0];
+ PyObject *op = (PyObject*)CHARACTER(ch);
+ assert(_Py_IsImmortal(op));
+ Py_SETREF(result, op);
+ }
}
else if (writer->use_bytearray) {
result = PyByteArray_FromStringAndSize(writer->small_buffer, size);
}
else {
+ // The function returns single byte singleton if size equals 1
result = PyBytes_FromStringAndSize(writer->small_buffer, size);
}
PyBytesWriter_Discard(writer);
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]