https://github.com/python/cpython/commit/7bbb9b57e67057d5ca3b7e3a434527fb3fcf5a2b
commit: 7bbb9b57e67057d5ca3b7e3a434527fb3fcf5a2b
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2024-03-14T22:23:00Z
summary:
gh-111696, PEP 737: Add %T and %N to PyUnicode_FromFormat() (#116839)
files:
A Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst
M Doc/c-api/unicode.rst
M Doc/whatsnew/3.13.rst
M Include/internal/pycore_typeobject.h
M Lib/test/test_capi/test_unicode.py
M Objects/typeobject.c
M Objects/unicodeobject.c
diff --git a/Doc/c-api/unicode.rst b/Doc/c-api/unicode.rst
index 666ffe89605c56..78eec14e3a24d6 100644
--- a/Doc/c-api/unicode.rst
+++ b/Doc/c-api/unicode.rst
@@ -518,6 +518,26 @@ APIs:
- :c:expr:`PyObject*`
- The result of calling :c:func:`PyObject_Repr`.
+ * - ``T``
+ - :c:expr:`PyObject*`
+ - Get the fully qualified name of an object type;
+ call :c:func:`PyType_GetFullyQualifiedName`.
+
+ * - ``T#``
+ - :c:expr:`PyObject*`
+ - Similar to ``T`` format, but use a colon (``:``) as separator between
+ the module name and the qualified name.
+
+ * - ``N``
+ - :c:expr:`PyTypeObject*`
+ - Get the fully qualified name of a type;
+ call :c:func:`PyType_GetFullyQualifiedName`.
+
+ * - ``N#``
+ - :c:expr:`PyTypeObject*`
+ - Similar to ``N`` format, but use a colon (``:``) as separator between
+ the module name and the qualified name.
+
.. 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 +573,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``, ``%T#``, ``%N`` and ``%N#`` formats 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 f42197c001f18f..856c6ee1d6e3f0 100644
--- a/Doc/whatsnew/3.13.rst
+++ b/Doc/whatsnew/3.13.rst
@@ -1668,6 +1668,12 @@ New Features
Equivalent to getting the ``type.__module__`` attribute.
(Contributed by Eric Snow and Victor Stinner in :gh:`111696`.)
+* Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
+ :c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
+ type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
+ more information.
+ (Contributed by Victor Stinner in :gh:`111696`.)
+
Porting to Python 3.13
----------------------
diff --git a/Include/internal/pycore_typeobject.h
b/Include/internal/pycore_typeobject.h
index 5c32d49e85c97b..8a25935f308178 100644
--- a/Include/internal/pycore_typeobject.h
+++ b/Include/internal/pycore_typeobject.h
@@ -150,6 +150,8 @@ extern PyTypeObject _PyBufferWrapper_Type;
PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
PyObject *name, int *meth_found);
+extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep);
+
#ifdef __cplusplus
}
diff --git a/Lib/test/test_capi/test_unicode.py
b/Lib/test/test_capi/test_unicode.py
index bb6161abf4da81..91c425e483f0ff 100644
--- a/Lib/test/test_capi/test_unicode.py
+++ b/Lib/test/test_capi/test_unicode.py
@@ -609,6 +609,40 @@ def check_format(expected, format, *args):
check_format('xyz',
b'%V', None, b'xyz')
+ # test %T
+ check_format('type: str',
+ b'type: %T', py_object("abc"))
+ check_format(f'type: st',
+ b'type: %.2T', py_object("abc"))
+ check_format(f'type: str',
+ b'type: %10T', py_object("abc"))
+
+ class LocalType:
+ pass
+ obj = LocalType()
+ fullname = f'{__name__}.{LocalType.__qualname__}'
+ check_format(f'type: {fullname}',
+ b'type: %T', py_object(obj))
+ fullname_alt = f'{__name__}:{LocalType.__qualname__}'
+ check_format(f'type: {fullname_alt}',
+ b'type: %T#', py_object(obj))
+
+ # test %N
+ check_format('type: str',
+ b'type: %N', py_object(str))
+ check_format(f'type: st',
+ b'type: %.2N', py_object(str))
+ check_format(f'type: str',
+ b'type: %10N', py_object(str))
+
+ check_format(f'type: {fullname}',
+ b'type: %N', py_object(type(obj)))
+ check_format(f'type: {fullname_alt}',
+ b'type: %N#', py_object(type(obj)))
+ with self.assertRaisesRegex(TypeError, "%N argument must be a type"):
+ check_format('type: str',
+ b'type: %N', py_object("abc"))
+
# 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/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst b/Misc/NEWS.d/next/C
API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst
new file mode 100644
index 00000000000000..44c15e4e6a8256
--- /dev/null
+++ b/Misc/NEWS.d/next/C API/2024-03-14-22-30-07.gh-issue-111696.76UMKi.rst
@@ -0,0 +1,4 @@
+Add support for ``%T``, ``%T#``, ``%N`` and ``%N#`` formats to
+:c:func:`PyUnicode_FromFormat`: format the fully qualified name of an object
+type and of a type: call :c:func:`PyType_GetModuleName`. See :pep:`737` for
+more information. Patch by Victor Stinner.
diff --git a/Objects/typeobject.c b/Objects/typeobject.c
index 1c5729c589da93..b73dfba37529a3 100644
--- a/Objects/typeobject.c
+++ b/Objects/typeobject.c
@@ -1208,7 +1208,7 @@ type_set_module(PyTypeObject *type, PyObject *value, void
*context)
PyObject *
-PyType_GetFullyQualifiedName(PyTypeObject *type)
+_PyType_GetFullyQualifiedName(PyTypeObject *type, char sep)
{
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
return PyUnicode_FromString(type->tp_name);
@@ -1230,7 +1230,7 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
&& !_PyUnicode_Equal(module, &_Py_ID(builtins))
&& !_PyUnicode_Equal(module, &_Py_ID(__main__)))
{
- result = PyUnicode_FromFormat("%U.%U", module, qualname);
+ result = PyUnicode_FromFormat("%U%c%U", module, sep, qualname);
}
else {
result = Py_NewRef(qualname);
@@ -1240,6 +1240,12 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
return result;
}
+PyObject *
+PyType_GetFullyQualifiedName(PyTypeObject *type)
+{
+ return _PyType_GetFullyQualifiedName(type, '.');
+}
+
static PyObject *
type_abstractmethods(PyTypeObject *type, void *context)
diff --git a/Objects/unicodeobject.c b/Objects/unicodeobject.c
index 0a569a950e88e2..c8f647a7a71135 100644
--- a/Objects/unicodeobject.c
+++ b/Objects/unicodeobject.c
@@ -2791,6 +2791,64 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
break;
}
+ case 'T':
+ {
+ PyObject *obj = va_arg(*vargs, PyObject *);
+ PyTypeObject *type = (PyTypeObject *)Py_NewRef(Py_TYPE(obj));
+
+ PyObject *type_name;
+ if (f[1] == '#') {
+ type_name = _PyType_GetFullyQualifiedName(type, ':');
+ f++;
+ }
+ else {
+ type_name = PyType_GetFullyQualifiedName(type);
+ }
+ Py_DECREF(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;
+ }
+
+ case 'N':
+ {
+ PyObject *type_raw = va_arg(*vargs, PyObject *);
+ assert(type_raw != NULL);
+
+ if (!PyType_Check(type_raw)) {
+ PyErr_SetString(PyExc_TypeError, "%N argument must be a type");
+ return NULL;
+ }
+ PyTypeObject *type = (PyTypeObject*)type_raw;
+
+ PyObject *type_name;
+ if (f[1] == '#') {
+ type_name = _PyType_GetFullyQualifiedName(type, ':');
+ f++;
+ }
+ else {
+ type_name = PyType_GetFullyQualifiedName(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);
_______________________________________________
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]