Skip to content

Commit c2b2df6

Browse files
committed
pythongh-111696, PEP 737: Add %T format to PyUnicode_FromFormat()
1 parent c432df6 commit c2b2df6

7 files changed

Lines changed: 131 additions & 2 deletions

File tree

‎Doc/c-api/unicode.rst‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,6 +518,26 @@ APIs:
518518
- :c:expr:`PyObject*`
519519
- The result of calling :c:func:`PyObject_Repr`.
520520
521+
* - ``T``
522+
- :c:expr:`PyObject*`
523+
- Get the fully qualified name of an object type;
524+
call :c:func:`PyType_GetFullyQualifiedName`.
525+
526+
* - ``T#``
527+
- :c:expr:`PyObject*`
528+
- Similar to ``T`` format, but use a colon (``:``) as separator between
529+
the module name and the qualified name.
530+
531+
* - ``N``
532+
- :c:expr:`PyTypeObject*`
533+
- Get the fully qualified name of a type;
534+
call :c:func:`PyType_GetFullyQualifiedName`.
535+
536+
* - ``N#``
537+
- :c:expr:`PyTypeObject*`
538+
- Similar to ``N`` format, but use a colon (``:``) as separator between
539+
the module name and the qualified name.
540+
521541
.. note::
522542
The width formatter unit is number of characters rather than bytes.
523543
The precision formatter unit is number of bytes or :c:type:`wchar_t`
@@ -553,6 +573,9 @@ APIs:
553573
In previous versions it caused all the rest of the format string to be
554574
copied as-is to the result string, and any extra arguments discarded.
555575
576+
.. versionchanged:: 3.13
577+
Support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats added.
578+
556579
557580
.. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)
558581

‎Doc/whatsnew/3.13.rst‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1668,6 +1668,12 @@ New Features
16681668
Equivalent to getting the ``type.__module__`` attribute.
16691669
(Contributed by Eric Snow and Victor Stinner in :gh:`111696`.)
16701670

1671+
* Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
1672+
:c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
1673+
type and of an type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
1674+
more information.
1675+
(Contributed by Victor Stinner in :gh:`111696`.)
1676+
16711677

16721678
Porting to Python 3.13
16731679
----------------------

‎Include/internal/pycore_typeobject.h‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ extern PyTypeObject _PyBufferWrapper_Type;
150150
PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
151151
PyObject *name, int *meth_found);
152152

153+
extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep);
154+
153155

154156
#ifdef __cplusplus
155157
}

‎Lib/test/test_capi/test_unicode.py‎

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,35 @@ def check_format(expected, format, *args):
609609
check_format('xyz',
610610
b'%V', None, b'xyz')
611611

612+
# test %T
613+
check_format('type: str',
614+
b'type: %T', py_object("abc"))
615+
class LocalType:
616+
pass
617+
obj = LocalType()
618+
fullname = f'{__name__}.{LocalType.__qualname__}'
619+
check_format(f'type: {fullname}',
620+
b'type: %T', py_object(obj))
621+
check_format(f'type: {fullname[:3]}',
622+
b'type: %.3T', py_object(obj))
623+
check_format(f'type: {fullname.rjust(20)}',
624+
b'type: %20T', py_object(obj))
625+
fullname_alt = f'{__name__}:{LocalType.__qualname__}'
626+
check_format(f'type: {fullname_alt}',
627+
b'type: %T#', py_object(obj))
628+
629+
# test %N
630+
check_format('type: str',
631+
b'type: %N', py_object(str))
632+
check_format(f'type: {fullname}',
633+
b'type: %N', py_object(type(obj)))
634+
check_format(f'type: {fullname_alt}',
635+
b'type: %N#', py_object(type(obj)))
636+
check_format(f'type: {fullname[:3]}',
637+
b'type: %.3N', py_object(type(obj)))
638+
check_format(f'type: {fullname.rjust(20)}',
639+
b'type: %20N', py_object(type(obj)))
640+
612641
# test %ls
613642
check_format('abc', b'%ls', c_wchar_p('abc'))
614643
check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11'))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
2+
:c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
3+
type and of an type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
4+
more information. Patch by Victor Stinner.

‎Objects/typeobject.c‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1208,7 +1208,7 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
12081208

12091209

12101210
PyObject *
1211-
PyType_GetFullyQualifiedName(PyTypeObject *type)
1211+
_PyType_GetFullyQualifiedName(PyTypeObject *type, char sep)
12121212
{
12131213
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
12141214
return PyUnicode_FromString(type->tp_name);
@@ -1230,7 +1230,7 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
12301230
&& !_PyUnicode_Equal(module, &_Py_ID(builtins))
12311231
&& !_PyUnicode_Equal(module, &_Py_ID(__main__)))
12321232
{
1233-
result = PyUnicode_FromFormat("%U.%U", module, qualname);
1233+
result = PyUnicode_FromFormat("%U%c%U", module, sep, qualname);
12341234
}
12351235
else {
12361236
result = Py_NewRef(qualname);
@@ -1240,6 +1240,12 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
12401240
return result;
12411241
}
12421242

1243+
PyObject *
1244+
PyType_GetFullyQualifiedName(PyTypeObject *type)
1245+
{
1246+
return _PyType_GetFullyQualifiedName(type, '.');
1247+
}
1248+
12431249

12441250
static PyObject *
12451251
type_abstractmethods(PyTypeObject *type, void *context)

‎Objects/unicodeobject.c‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,65 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
27912791
break;
27922792
}
27932793

2794+
case 'T':
2795+
{
2796+
PyObject *obj = va_arg(*vargs, PyObject *);
2797+
PyTypeObject *type = (PyTypeObject *)Py_NewRef(Py_TYPE(obj));
2798+
2799+
PyObject *type_name;
2800+
if (f[1] == '#') {
2801+
type_name = _PyType_GetFullyQualifiedName(type, ':');
2802+
f++;
2803+
}
2804+
else {
2805+
type_name = PyType_GetFullyQualifiedName(type);
2806+
}
2807+
Py_DECREF(type);
2808+
if (!type_name) {
2809+
return NULL;
2810+
}
2811+
2812+
if (unicode_fromformat_write_str(writer, type_name,
2813+
width, precision, flags) == -1) {
2814+
Py_DECREF(type_name);
2815+
return NULL;
2816+
}
2817+
Py_DECREF(type_name);
2818+
break;
2819+
}
2820+
2821+
case 'N':
2822+
{
2823+
PyObject *type_raw = va_arg(*vargs, PyObject *);
2824+
assert(type_raw != NULL);
2825+
2826+
if (!PyType_Check(type_raw)) {
2827+
PyErr_SetString(PyExc_TypeError,
2828+
"%T argument must be a type; did you forget Py_TYPE()?");
2829+
return NULL;
2830+
}
2831+
PyTypeObject *type = (PyTypeObject*)type_raw;
2832+
2833+
PyObject *type_name;
2834+
if (f[1] == '#') {
2835+
type_name = _PyType_GetFullyQualifiedName(type, ':');
2836+
f++;
2837+
}
2838+
else {
2839+
type_name = PyType_GetFullyQualifiedName(type);
2840+
}
2841+
if (!type_name) {
2842+
return NULL;
2843+
}
2844+
if (unicode_fromformat_write_str(writer, type_name,
2845+
width, precision, flags) == -1) {
2846+
Py_DECREF(type_name);
2847+
return NULL;
2848+
}
2849+
Py_DECREF(type_name);
2850+
break;
2851+
}
2852+
27942853
default:
27952854
invalid_format:
27962855
PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);

0 commit comments

Comments
 (0)