diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst index e654412965a727d..37b62b6090f8e49 100644 --- a/Doc/c-api/unicode.rst +++ b/Doc/c-api/unicode.rst @@ -518,6 +518,11 @@ APIs: - :c:expr:`PyObject*` - The result of calling :c:func:`PyObject_Repr`. + * - ``T`` + - :c:expr:`PyObject*` + - Get the name of a type (``type.__name__``): the result of calling + ``PyType_GetName(type)``. + .. note:: The width formatter unit is number of characters rather than bytes. The precision formatter unit is number of bytes or :c:type:`wchar_t` @@ -553,6 +558,9 @@ APIs: In previous versions it caused all the rest of the format string to be copied as-is to the result string, and any extra arguments discarded. + .. versionchanged:: 3.13 + Support for ``"%T"`` added. + .. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index ae3c88d28d73a08..91124ed12b6a1af 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -1164,6 +1164,10 @@ New Features :c:func:`PyErr_WriteUnraisable`, but allow to customize the warning mesage. (Contributed by Serhiy Storchaka in :gh:`108082`.) +* Add support for ``"%T"`` format to :c:func:`PyUnicode_FromFormat`: format + the name of a type (``type.__name__``). + (Contributed by Victor Stinner in :gh:`111696`.) + Porting to Python 3.13 ---------------------- diff --git a/Lib/test/test_capi/test_unicode.py b/Lib/test/test_capi/test_unicode.py index bb6161abf4da81d..b2175cc95c6b709 100644 --- a/Lib/test/test_capi/test_unicode.py +++ b/Lib/test/test_capi/test_unicode.py @@ -609,6 +609,23 @@ def check_format(expected, format, *args): check_format('xyz', b'%V', None, b'xyz') + # test %T and %#T + obj = 'abc' + obj_type = type(obj) + check_format('type: str', + b'type: %T', py_object(obj_type)) + class LocalType: + pass + obj = LocalType() + obj_type = type(obj) + name = 'LocalType' + check_format(f'type: {name}', + b'type: %T', py_object(obj_type)) + check_format(f'type: {name[:3]}', + b'type: %.3T', py_object(obj_type)) + check_format(f'type: {name.rjust(20)}', + b'type: %20T', py_object(obj_type)) + # test %ls check_format('abc', b'%ls', c_wchar_p('abc')) check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11')) diff --git a/Misc/NEWS.d/next/C API/2023-11-03-15-53-11.gh-issue-111696.LObNGG.rst b/Misc/NEWS.d/next/C API/2023-11-03-15-53-11.gh-issue-111696.LObNGG.rst new file mode 100644 index 000000000000000..d669db8ed0c6fa1 --- /dev/null +++ b/Misc/NEWS.d/next/C API/2023-11-03-15-53-11.gh-issue-111696.LObNGG.rst @@ -0,0 +1,2 @@ +Add support for ``"%T"`` format to :c:func:`PyUnicode_FromFormat`: format the +name of a type (``type.__name__``). Patch by Victor Stinner. diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c index 53e1e56babf952b..00ca74bb691798a 100644 --- a/Objects/unicodeobject.c +++ b/Objects/unicodeobject.c @@ -2464,6 +2464,7 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer, switch (*f++) { case '-': flags |= F_LJUST; continue; case '0': flags |= F_ZERO; continue; + case '#': flags |= F_ALT; continue; } f--; break; @@ -2787,6 +2788,37 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer, break; } + case 'T': + { + PyObject *type_raw = va_arg(*vargs, PyObject *); + assert(type_raw != NULL); + + if (!PyType_Check(type_raw)) { + PyErr_SetString(PyExc_TypeError, + "%T argument must be a type; did you forget Py_TYPE()?"); + return NULL; + } + PyTypeObject *type = (PyTypeObject*)type_raw; + + PyObject *type_name; + if (flags & F_ALT) { + type_name = _PyType_GetFullyQualName(type, 0); + } + else { + type_name = PyType_GetName(type); + } + if (!type_name) { + return NULL; + } + if (unicode_fromformat_write_str(writer, type_name, + width, precision, flags) == -1) { + Py_DECREF(type_name); + return NULL; + } + Py_DECREF(type_name); + break; + } + default: invalid_format: PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);