From 654ff70e89b4ccec149de12522deba7db4bbf3c9 Mon Sep 17 00:00:00 2001 From: Walter Doerwald Date: Mon, 8 Jun 2026 20:50:31 +0200 Subject: [PATCH] gh-151099: Better repr output for template strings. --- Lib/test/test_string/test_templatelib.py | 19 ++++++ ...-06-08-20-38-28.gh-issue-151099.6Opls1.rst | 2 + Objects/templateobject.c | 68 +++++++++++++++++-- 3 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-08-20-38-28.gh-issue-151099.6Opls1.rst diff --git a/Lib/test/test_string/test_templatelib.py b/Lib/test/test_string/test_templatelib.py index 1c86717155fd5ab..d43fe73c453ab7c 100644 --- a/Lib/test/test_string/test_templatelib.py +++ b/Lib/test/test_string/test_templatelib.py @@ -101,6 +101,25 @@ def test_template_values(self): t = t'Hello, {name}, {age} from {country}' self.assertEqual(t.values, ("Lys", 0, "GR")) + def test_repr(self): + self.assertEqual(repr(t''), 'Template()') + self.assertEqual(repr(t'foo'), "Template('foo')") + x = 42 + self.assertEqual( + repr(t'{x}'), + "Template(Interpolation(42, 'x', None, ''))") + self.assertEqual( + repr(t'a{x!r:02}b'), + "Template('a', Interpolation(42, 'x', 'r', '02'), 'b')") + + # Test a "recursive" template + x = [] + t = t'a{x}b' + x.append(t) + self.assertEqual( + repr(t), + "Template('a', Interpolation([Template(...)], 'x', None, ''), 'b')") + def test_pickle_template(self): user = 'test' for template in ( diff --git a/Misc/NEWS.d/next/Library/2026-06-08-20-38-28.gh-issue-151099.6Opls1.rst b/Misc/NEWS.d/next/Library/2026-06-08-20-38-28.gh-issue-151099.6Opls1.rst new file mode 100644 index 000000000000000..6720b7fb8447ab2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-06-08-20-38-28.gh-issue-151099.6Opls1.rst @@ -0,0 +1,2 @@ +Change :func:`repr` output of :class:`string.templatelib.Template` objects +to list the parts of the template in their original source order. diff --git a/Objects/templateobject.c b/Objects/templateobject.c index 1609e82b444516c..24aa73cbb8c211f 100644 --- a/Objects/templateobject.c +++ b/Objects/templateobject.c @@ -212,10 +212,70 @@ static PyObject * template_repr(PyObject *op) { templateobject *self = templateobject_CAST(op); - return PyUnicode_FromFormat("%s(strings=%R, interpolations=%R)", - _PyType_Name(Py_TYPE(self)), - self->strings, - self->interpolations); + + int res = Py_ReprEnter(self); + if (res != 0) { + return (res > 0 ? PyUnicode_FromString("Template(...)") : NULL); + } + + Py_ssize_t stringslen = PyTuple_GET_SIZE(self->strings); + Py_ssize_t interpolationslen = PyTuple_GET_SIZE(self->interpolations); + + PyUnicodeWriter *writer = PyUnicodeWriter_Create(10); + if (writer == NULL) { + return NULL; + } + + if (PyUnicodeWriter_WriteUTF8(writer, _PyType_Name(Py_TYPE(self)), -1) < 0) { + goto error; + } + if (PyUnicodeWriter_WriteChar(writer, '(') < 0) { + goto error; + } + + /* Render the strings and interpolations in the order they appeared in the + constructor call, i.e. interleaved and skipping empty strings. This + matches the order produced by templateiter_next. */ + int first = 1; + for (Py_ssize_t i = 0; i < stringslen; i++) { + PyObject *string = PyTuple_GET_ITEM(self->strings, i); + if (PyUnicode_GET_LENGTH(string) > 0) { + if (!first) { + if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) { + goto error; + } + } + if (PyUnicodeWriter_WriteRepr(writer, string) < 0) { + goto error; + } + first = 0; + } + if (i < interpolationslen) { + PyObject *interpolation = PyTuple_GET_ITEM(self->interpolations, i); + if (!first) { + if (PyUnicodeWriter_WriteASCII(writer, ", ", 2) < 0) { + goto error; + } + } + if (PyUnicodeWriter_WriteRepr(writer, interpolation) < 0) { + goto error; + } + first = 0; + } + } + + if (PyUnicodeWriter_WriteChar(writer, ')') < 0) { + goto error; + } + + Py_ReprLeave(self); + + return PyUnicodeWriter_Finish(writer); + +error: + Py_ReprLeave(self); + PyUnicodeWriter_Discard(writer); + return NULL; } static PyObject *