8000 gh-111696, PEP 737: Add %T and %N to PyUnicode_FromFormat() (#116839) · python/cpython@7bbb9b5 · GitHub
[go: up one dir, main page]

Skip to content

Commit 7bbb9b5

Browse files
authored
gh-111696, PEP 737: Add %T and %N to PyUnicode_FromFormat() (#116839)
1 parent 5f52d20 commit 7bbb9b5

File tree

7 files changed

+135
-2
lines changed

7 files changed

+135
-2
lines changed

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 a 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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,40 @@ 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+
check_format(f'type: st',
616+
b'type: %.2T', py_object("abc"))
617+
check_format(f'type: str',
618+
b'type: %10T', py_object("abc"))
619+
620+
class LocalType:
621+
pass
622+
obj = LocalType()
623+
fullname = f'{__name__}.{LocalType.__qualname__}'
624+
check_format(f'type: {fullname}',
625+
b'type: %T', py_object(obj))
626+
fullname_alt = f'{__name__}:{LocalType.__qualname__}'
627+
check_format(f'type: {fullname_alt}',
628+
b'type: %T#', py_object(obj))
629+
630+
# test %N
631+
check_format('type: str',
632+
b'type: %N', py_object(str))
633+
check_format(f'type: st',
634+
b'type: %.2N', py_object(str))
635+
check_format(f'type: str',
636+
b'type: %10N', py_object(str))
637+
638+
check_format(f'type: {fullname}',
639+
b'type: %N', py_object(type(obj)))
640+
check_format(f'type: {fullname_alt}',
641+
b'type: %N#', py_object(type(obj)))
642+
with self.assertRaisesRegex(TypeError, "%N argument must be a type"):
643+
check_format('type: str',
644+
b'type: %N', py_object("abc"))
645+
612646
# test %ls
613647
check_format('abc', b'%ls', c_wchar_p('abc'))
614648
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 a 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: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2791,6 +2791,64 @@ 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, "%N argument must be a type");
2828+
return NULL;
2829+
}
2830+
PyTypeObject *type = (PyTypeObject*)type_raw;
2831+
2832+
PyObject *type_name;
2833+
if (f[1] == '#') {
2834+
type_name = _PyType_GetFullyQualifiedName(type, ':');
2835+
f++;
2836+
}
2837+
else {
2838+
type_name = PyType_GetFullyQualifiedName(type);
2839+
}
2840+
if (!type_name) {
2841+
return NULL;
2842+
}
2843+
if (unicode_fromformat_write_str(writer, type_name,
2844+
width, precision, flags) == -1) {
2845+
Py_DECREF(type_name);
2846+
return NULL;
2847+
}
2848+
Py_DECREF(type_name);
2849+
break;
2850+
}
2851+
27942852
default:
27952853
invalid_format:
27962854
PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);

0 commit comments

Comments
 (0)
0