https://github.com/python/cpython/commit/a64b7ecba4db5480504a1a83ae6e80c81472fe16
commit: a64b7ecba4db5480504a1a83ae6e80c81472fe16
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-09-20T16:25:20+02:00
summary:

gh-157710: Avoid resize in PyUnicodeWriter_Finish() for singleton (#157862)

PyBytesWriter_FinishWithSize() and PyUnicodeWriter_Finish() now
discard the output string instead of resizing it if the single
byte/character singleton is used.

files:
M Lib/test/test_capi/test_unicode.py
M Objects/bytesobject.c
M Objects/unicode_writer.c

diff --git a/Lib/test/test_capi/test_unicode.py 
b/Lib/test/test_capi/test_unicode.py
index f9e7b5ab041962..f13ad6f428ec09 100644
--- a/Lib/test/test_capi/test_unicode.py
+++ b/Lib/test/test_capi/test_unicode.py
@@ -2050,12 +2050,19 @@ def test_singletons(self):
         for size in (0, 123):
             with self.subTest(size=size):
                 writer = self.create_writer(size)
+                writer.write_utf8(b'utf8', 0)
+                writer.write_ascii(b'ascii', 0)
+                writer.write_widechar(b'wstr', 0)
+                writer.write_ucs4(b'ucs4', 0)
+                writer.write_substring('text', 0, 0)
                 self.assertIs(writer.finish(), '')
 
         for ch in range(256):
             with self.subTest(ch=ch):
                 ch = chr(ch)
                 writer = self.create_writer(0)
+                # Use PyUnicodeWriter_WriteSubstring() to avoid the read-only
+                # buffer optimization
                 writer.write_substring(ch + 'xxx', 0, 1)
                 self.assertIs(writer.finish(), ch)
 
diff --git a/Objects/bytesobject.c b/Objects/bytesobject.c
index 7fefd64eefb2ac..91cbfa23e30b20 100644
--- a/Objects/bytesobject.c
+++ b/Objects/bytesobject.c
@@ -3938,12 +3938,6 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, 
Py_ssize_t size)
             }
         }
         else {
-            if (size != PyBytes_GET_SIZE(writer->obj)) {
-                if (_PyBytes_Resize(&writer->obj, size)) {
-                    goto error;
-                }
-            }
-
             if (size == 1) {
                 // Get the single byte singleton
                 unsigned char ch = PyBytes_AS_STRING(writer->obj)[0];
@@ -3951,6 +3945,11 @@ PyBytesWriter_FinishWithSize(PyBytesWriter *writer, 
Py_ssize_t size)
                 assert(_Py_IsImmortal(op));
                 Py_SETREF(writer->obj, op);
             }
+            else if (size != PyBytes_GET_SIZE(writer->obj)) {
+                if (_PyBytes_Resize(&writer->obj, size)) {
+                    goto error;
+                }
+            }
         }
 
         result = writer->obj;
diff --git a/Objects/unicode_writer.c b/Objects/unicode_writer.c
index 92e0db08b9de00..751fca9948598f 100644
--- a/Objects/unicode_writer.c
+++ b/Objects/unicode_writer.c
@@ -609,8 +609,6 @@ _PyUnicodeWriter_WriteLatin1String(_PyUnicodeWriter *writer,
 PyObject *
 _PyUnicodeWriter_Finish(_PyUnicodeWriter *writer)
 {
-    PyObject *str;
-
 #ifdef Py_DEBUG
     // Check for buffer overflow
     if (writer->buffer != NULL) {
@@ -625,23 +623,28 @@ _PyUnicodeWriter_Finish(_PyUnicodeWriter *writer)
     }
 #endif
 
-    if (writer->pos == 0) {
-        Py_CLEAR(writer->buffer);
-        return _PyUnicode_GetEmpty();
-    }
-
-    str = writer->buffer;
+    PyObject *str = writer->buffer;
     writer->buffer = NULL;
 
-    if (writer->readonly) {
-        assert(PyUnicode_GET_LENGTH(str) == writer->pos);
-        assert(_PyUnicode_CheckConsistency(str, 1));
-        return str;
+    Py_ssize_t final_size = writer->pos;
+    if (final_size == 0) {
+        PyObject *empty = _PyUnicode_GetEmpty();
+        Py_XDECREF(str);  // writer->buffer can be NULL if the position is 0
+        return empty;
     }
 
-    if (PyUnicode_GET_LENGTH(str) != writer->pos) {
-        PyObject *str2;
-        str2 = _PyUnicode_ResizeCompact(str, writer->pos);
+    Py_ssize_t length = PyUnicode_GET_LENGTH(str);
+    if (final_size == 1 && PyUnicode_KIND(str) == PyUnicode_1BYTE_KIND) {
+        assert(length >= 1);
+        const Py_UCS1 *data = PyUnicode_1BYTE_DATA(str);
+        Py_UCS1 ch = data[0];
+        PyObject *latin1_char = _Py_LATIN1_CHR(ch);
+        Py_DECREF(str);
+        return latin1_char;
+    }
+
+    if (!writer->readonly && length != final_size) {
+        PyObject *str2 = _PyUnicode_ResizeCompact(str, final_size);
         if (str2 == NULL) {
             Py_DECREF(str);
             return NULL;
@@ -650,7 +653,7 @@ _PyUnicodeWriter_Finish(_PyUnicodeWriter *writer)
     }
 
     assert(_PyUnicode_CheckConsistency(str, 1));
-    return _PyUnicode_Result(str);
+    return str;
 }
 
 

_______________________________________________
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]

Reply via email to