gh-111696, PEP 737: Add %T and %N to PyUnicode_FromFormat() (#116839)

This commit is contained in:
Victor Stinner 2024-03-14 23:23:00 +01:00 committed by GitHub
parent 5f52d20a93
commit 7bbb9b57e6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 135 additions and 2 deletions

View File

@ -518,6 +518,26 @@ APIs:
- :c:expr:`PyObject*` - :c:expr:`PyObject*`
- The result of calling :c:func:`PyObject_Repr`. - 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:: .. note::
The width formatter unit is number of characters rather than bytes. The width formatter unit is number of characters rather than bytes.
The precision formatter unit is number of bytes or :c:type:`wchar_t` 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 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. 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) .. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)

View File

@ -1668,6 +1668,12 @@ New Features
Equivalent to getting the ``type.__module__`` attribute. Equivalent to getting the ``type.__module__`` attribute.
(Contributed by Eric Snow and Victor Stinner in :gh:`111696`.) (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 Porting to Python 3.13
---------------------- ----------------------

View File

@ -150,6 +150,8 @@ extern PyTypeObject _PyBufferWrapper_Type;
PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj, PyAPI_FUNC(PyObject*) _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
PyObject *name, int *meth_found); PyObject *name, int *meth_found);
extern PyObject* _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep);
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@ -609,6 +609,40 @@ class CAPITest(unittest.TestCase):
check_format('xyz', check_format('xyz',
b'%V', None, b'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 # test %ls
check_format('abc', b'%ls', c_wchar_p('abc')) check_format('abc', b'%ls', c_wchar_p('abc'))
check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11')) check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11'))

View File

@ -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.

View File

@ -1208,7 +1208,7 @@ type_set_module(PyTypeObject *type, PyObject *value, void *context)
PyObject * PyObject *
PyType_GetFullyQualifiedName(PyTypeObject *type) _PyType_GetFullyQualifiedName(PyTypeObject *type, char sep)
{ {
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) { if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
return PyUnicode_FromString(type->tp_name); 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(builtins))
&& !_PyUnicode_Equal(module, &_Py_ID(__main__))) && !_PyUnicode_Equal(module, &_Py_ID(__main__)))
{ {
result = PyUnicode_FromFormat("%U.%U", module, qualname); result = PyUnicode_FromFormat("%U%c%U", module, sep, qualname);
} }
else { else {
result = Py_NewRef(qualname); result = Py_NewRef(qualname);
@ -1240,6 +1240,12 @@ PyType_GetFullyQualifiedName(PyTypeObject *type)
return result; return result;
} }
PyObject *
PyType_GetFullyQualifiedName(PyTypeObject *type)
{
return _PyType_GetFullyQualifiedName(type, '.');
}
static PyObject * static PyObject *
type_abstractmethods(PyTypeObject *type, void *context) type_abstractmethods(PyTypeObject *type, void *context)

View File

@ -2791,6 +2791,64 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
break; 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: default:
invalid_format: invalid_format:
PyErr_Format(PyExc_SystemError, "invalid format string: %s", p); PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);