Skip to content

Commit 50923bf

Browse files
committed
gh-111696: Add %T format to PyUnicode_FromFormat()
* Add "%T" and "%#T" formats to PyUnicode_FromFormat(). * Add type.__fullyqualname__ read-only attribute.
1 parent f62c7cc commit 50923bf

File tree

11 files changed

+171
-22
lines changed

11 files changed

+171
-22
lines changed

Doc/c-api/unicode.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,8 @@ APIs:
423423
| ``-`` | The converted value is left adjusted (overrides the ``0`` |
424424
| | flag if both are given). |
425425
+-------+-------------------------------------------------------------+
426+
| ``#`` | Alternate form |
427+
+-------+-------------------------------------------------------------+
426428
427429
The length modifiers for following integer conversions (``d``, ``i``,
428430
``o``, ``u``, ``x``, or ``X``) specify the type of the argument
@@ -518,6 +520,17 @@ APIs:
518520
- :c:expr:`PyObject*`
519521
- The result of calling :c:func:`PyObject_Repr`.
520522
523+
* - ``T``
524+
- :c:expr:`PyObject*`
525+
- Get the name of an object type (``type.__name__``): the result of calling
526+
``PyType_GetName(Py_TYPE(obj))``.
527+
528+
* - ``#T``
529+
- :c:expr:`PyObject*`
530+
- Get the fully qualified name of an object type
531+
(:attr:`class.__fullyqualname__`): the result of calling
532+
``PyObject_GetAttrString(Py_TYPE(obj), "__fullyqualname__")``.
533+
521534
.. note::
522535
The width formatter unit is number of characters rather than bytes.
523536
The precision formatter unit is number of bytes or :c:type:`wchar_t`
@@ -553,6 +566,9 @@ APIs:
553566
In previous versions it caused all the rest of the format string to be
554567
copied as-is to the result string, and any extra arguments discarded.
555568
569+
.. versionchanged:: 3.13
570+
Support for ``"%T"`` and ``"%#T"`` added.
571+
556572
557573
.. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)
558574

Doc/library/stdtypes.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5493,6 +5493,16 @@ types, where they are relevant. Some of these are not reported by the
54935493
.. versionadded:: 3.3
54945494

54955495

5496+
.. attribute:: class.__fullyqualname__
5497+
5498+
The fully qualified name of the class instance:
5499+
``f"{class.__module__}.{class.__qualname__}"``, or
5500+
``f"{class.__qualname__}"`` if ``class.__module__`` is equal to
5501+
``"builtins"``.
5502+
5503+
.. versionadded:: 3.13
5504+
5505+
54965506
.. attribute:: definition.__type_params__
54975507

54985508
The :ref:`type parameters <type-params>` of generic classes, functions,

Doc/library/string.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,20 @@ The available presentation types for :class:`float` and
588588
| | as altered by the other format modifiers. |
589589
+---------+----------------------------------------------------------+
590590

591+
The available presentation types for :class:`type` values are:
592+
593+
+----------+----------------------------------------------------------+
594+
| Type | Meaning |
595+
+==========+==========================================================+
596+
| ``'T'`` | Format the type name (``type.__name__``). |
597+
+----------+----------------------------------------------------------+
598+
| ``'#T'`` | Format the type fully qualified name |
599+
| | (:attr:`class.__fullyqualname__`). |
600+
+----------+----------------------------------------------------------+
601+
602+
.. versionchanged:: 3.13
603+
Add ``T`` and ``T#`` formats for :class:`type` values.
604+
591605

592606
.. _formatexamples:
593607

Doc/whatsnew/3.13.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,11 @@ Other Language Changes
125125
equivalent of the :option:`-X frozen_modules <-X>` command-line option.
126126
(Contributed by Yilei Yang in :gh:`111374`.)
127127

128+
* Add :attr:`__fullyqualname__ <class.__fullyqualname__>` read-only attribute
129+
to types: the fully qualified type name.
130+
(Contributed by Victor Stinner in :gh:`111696`.)
131+
132+
128133
New Modules
129134
===========
130135

@@ -1127,6 +1132,12 @@ New Features
11271132
* Add :c:func:`PyUnicode_AsUTF8` function to the limited C API.
11281133
(Contributed by Victor Stinner in :gh:`111089`.)
11291134

1135+
* Add support for ``"%T"`` and ``"%#T"`` formats to
1136+
:c:func:`PyUnicode_FromFormat`: ``"%T"`` formats the name of an object type
1137+
(``type.__name__``) and ``"%#T"`` formats the fully qualifed name of an
1138+
object type (:attr:`class.__fullyqualname__`).
1139+
(Contributed by Victor Stinner in :gh:`111696`.)
1140+
11301141

11311142
Porting to Python 3.13
11321143
----------------------

Include/internal/pycore_typeobject.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ extern PyTypeObject _PyBufferWrapper_Type;
143143
extern PyObject* _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
144144
PyObject *name, int *meth_found);
145145

146+
extern PyObject* _PyType_GetFullyQualName(
147+
PyTypeObject *type,
148+
int ignore_module_error);
149+
146150
#ifdef __cplusplus
147151
}
148152
#endif

Lib/test/test_builtin.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2430,6 +2430,7 @@ def test_new_type(self):
24302430
self.assertEqual(A.__name__, 'A')
24312431
self.assertEqual(A.__qualname__, 'A')
24322432
self.assertEqual(A.__module__, __name__)
2433+
self.assertEqual(A.__fullyqualname__, f'{__name__}.A')
24332434
self.assertEqual(A.__bases__, (object,))
24342435
self.assertIs(A.__base__, object)
24352436
x = A()
@@ -2443,6 +2444,7 @@ def ham(self):
24432444
self.assertEqual(C.__name__, 'C')
24442445
self.assertEqual(C.__qualname__, 'C')
24452446
self.assertEqual(C.__module__, __name__)
2447+
self.assertEqual(C.__fullyqualname__, f'{__name__}.C')
24462448
self.assertEqual(C.__bases__, (B, int))
24472449
self.assertIs(C.__base__, int)
24482450
self.assertIn('spam', C.__dict__)
@@ -2468,6 +2470,7 @@ def test_type_name(self):
24682470
self.assertEqual(A.__name__, name)
24692471
self.assertEqual(A.__qualname__, name)
24702472
self.assertEqual(A.__module__, __name__)
2473+
self.assertEqual(A.__fullyqualname__, f'{__name__}.{name}')
24712474
with self.assertRaises(ValueError):
24722475
type('A\x00B', (), {})
24732476
with self.assertRaises(UnicodeEncodeError):
@@ -2482,6 +2485,7 @@ def test_type_name(self):
24822485
self.assertEqual(C.__name__, name)
24832486
self.assertEqual(C.__qualname__, 'C')
24842487
self.assertEqual(C.__module__, __name__)
2488+
self.assertEqual(C.__fullyqualname__, f'{__name__}.C')
24852489

24862490
A = type('C', (), {})
24872491
with self.assertRaises(ValueError):
@@ -2499,13 +2503,15 @@ def test_type_qualname(self):
24992503
self.assertEqual(A.__name__, 'A')
25002504
self.assertEqual(A.__qualname__, 'B.C')
25012505
self.assertEqual(A.__module__, __name__)
2506+
self.assertEqual(A.__fullyqualname__, f'{__name__}.B.C')
25022507
with self.assertRaises(TypeError):
25032508
type('A', (), {'__qualname__': b'B'})
25042509
self.assertEqual(A.__qualname__, 'B.C')
25052510

25062511
A.__qualname__ = 'D.E'
25072512
self.assertEqual(A.__name__, 'A')
25082513
self.assertEqual(A.__qualname__, 'D.E')
2514+
self.assertEqual(A.__fullyqualname__, f'{__name__}.D.E')
25092515
with self.assertRaises(TypeError):
25102516
A.__qualname__ = b'B'
25112517
self.assertEqual(A.__qualname__, 'D.E')

Lib/test/test_capi/test_unicode.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -584,6 +584,24 @@ def check_format(expected, format, *args):
584584
check_format('xyz',
585585
b'%V', None, b'xyz')
586586

587+
# test %T and %#T
588+
check_format('type: str',
589+
b'type: %T', 'abc')
590+
check_format('type: str',
591+
b'type: %#T', 'abc')
592+
class LocalType:
593+
pass
594+
obj = LocalType()
595+
name = 'LocalType'
596+
check_format(f'type: {name}',
597+
b'type: %T', py_object(obj))
598+
check_format(f'type: {name[:3]}',
599+
b'type: %.3T', py_object(obj))
600+
check_format(f'type: {name.rjust(20)}',
601+
b'type: %20T', py_object(obj))
602+
check_format(f'type: {LocalType.__module__}.{LocalType.__qualname__}',
603+
b'type: %#T', py_object(obj))
604+
587605
# test %ls
588606
check_format('abc', b'%ls', c_wchar_p('abc'))
589607
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"`` and ``"%#T"`` formats to
2+
:c:func:`PyUnicode_FromFormat`: ``"%T"`` formats the name of an object type
3+
(``type.__name__``) and ``"%#T"`` formats the fully qualifed name of an object
4+
type (:attr:`class.__fullyqualname__`). Patch by Victor Stinner.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add :attr:`__fullyqualname__ <class.__fullyqualname__>` read-only attribute
2+
to types: the fully qualified type name. Patch by Victor Stinner.

Objects/typeobject.c

Lines changed: 62 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,6 +1036,12 @@ type_qualname(PyTypeObject *type, void *context)
10361036
}
10371037
}
10381038

1039+
static PyObject *
1040+
type_fullyqualname(PyTypeObject *type, void *context)
1041+
{
1042+
return _PyType_GetFullyQualName(type, 0);
1043+
}
1044+
10391045
static int
10401046
type_set_name(PyTypeObject *type, PyObject *value, void *context)
10411047
{
@@ -1598,6 +1604,7 @@ type___subclasscheck___impl(PyTypeObject *self, PyObject *subclass)
15981604
static PyGetSetDef type_getsets[] = {
15991605
{"__name__", (getter)type_name, (setter)type_set_name, NULL},
16001606
{"__qualname__", (getter)type_qualname, (setter)type_set_qualname, NULL},
1607+
{"__fullyqualname__", (getter)type_fullyqualname, NULL, NULL},
16011608
{"__bases__", (getter)type_get_bases, (setter)type_set_bases, NULL},
16021609
{"__mro__", (getter)type_get_mro, NULL, NULL},
16031610
{"__module__", (getter)type_module, (setter)type_set_module, NULL},
@@ -1615,33 +1622,18 @@ static PyObject *
16151622
type_repr(PyTypeObject *type)
16161623
{
16171624
if (type->tp_name == NULL) {
1618-
// type_repr() called before the type is fully initialized
1619-
// by PyType_Ready().
1625+
// If type_repr() is called before the type is fully initialized
1626+
// by PyType_Ready(), just format the type memory address.
16201627
return PyUnicode_FromFormat("<class at %p>", type);
16211628
}
16221629

1623-
PyObject *mod, *name, *rtn;
1624-
1625-
mod = type_module(type, NULL);
1626-
if (mod == NULL)
1627-
PyErr_Clear();
1628-
else if (!PyUnicode_Check(mod)) {
1629-
Py_SETREF(mod, NULL);
1630-
}
1631-
name = type_qualname(type, NULL);
1632-
if (name == NULL) {
1633-
Py_XDECREF(mod);
1630+
PyObject *fullqualname = _PyType_GetFullyQualName(type, 1);
1631+
if (fullqualname == NULL) {
16341632
return NULL;
16351633
}
1636-
1637-
if (mod != NULL && !_PyUnicode_Equal(mod, &_Py_ID(builtins)))
1638-
rtn = PyUnicode_FromFormat("<class '%U.%U'>", mod, name);
1639-
else
1640-
rtn = PyUnicode_FromFormat("<class '%s'>", type->tp_name);
1641-
1642-
Py_XDECREF(mod);
1643-
Py_DECREF(name);
1644-
return rtn;
1634+
PyObject *result = PyUnicode_FromFormat("<class '%U'>", fullqualname);
1635+
Py_DECREF(fullqualname);
1636+
return result;
16451637
}
16461638

16471639
static PyObject *
@@ -4560,6 +4552,53 @@ PyType_GetQualName(PyTypeObject *type)
45604552
return type_qualname(type, NULL);
45614553
}
45624554

4555+
4556+
PyObject*
4557+
_PyType_GetFullyQualName(PyTypeObject *type, int ignore_module_error)
4558+
{
4559+
// type is a static type and PyType_Ready() was not called on it yet?
4560+
if (type->tp_name == NULL) {
4561+
PyErr_SetString(PyExc_TypeError, "static type not initialized");
4562+
return NULL;
4563+
}
4564+
4565+
if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
4566+
// Static type
4567+
return PyUnicode_FromString(type->tp_name);
4568+
}
4569+
4570+
PyObject *qualname = type_qualname(type, NULL);
4571+
if (qualname == NULL) {
4572+
return NULL;
4573+
}
4574+
4575+
PyObject *module = type_module(type, NULL);
4576+
if (module == NULL) {
4577+
if (ignore_module_error) {
4578+
// type_repr() ignores type_module() errors
4579+
PyErr_Clear();
4580+
return qualname;
4581+
}
4582+
4583+
Py_DECREF(qualname);
4584+
return NULL;
4585+
}
4586+
4587+
PyObject *result;
4588+
if (PyUnicode_Check(module)
4589+
&& !_PyUnicode_Equal(module, &_Py_ID(builtins)))
4590+
{
4591+
result = PyUnicode_FromFormat("%U.%U", module, qualname);
4592+
}
4593+
else {
4594+
result = Py_NewRef(qualname);
4595+
}
4596+
Py_DECREF(module);
4597+
Py_DECREF(qualname);
4598+
return result;
4599+
}
4600+
4601+
45634602
void *
45644603
PyType_GetSlot(PyTypeObject *type, int slot)
45654604
{
@@ -5250,6 +5289,7 @@ type___sizeof___impl(PyTypeObject *self)
52505289
return PyLong_FromSize_t(size);
52515290
}
52525291

5292+
52535293
static PyMethodDef type_methods[] = {
52545294
TYPE_MRO_METHODDEF
52555295
TYPE___SUBCLASSES___METHODDEF

Objects/unicodeobject.c

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,6 +2464,7 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
24642464
switch (*f++) {
24652465
case '-': flags |= F_LJUST; continue;
24662466
case '0': flags |= F_ZERO; continue;
2467+
case '#': flags |= F_ALT; continue;
24672468
}
24682469
f--;
24692470
break;
@@ -2787,6 +2788,29 @@ unicode_fromformat_arg(_PyUnicodeWriter *writer,
27872788
break;
27882789
}
27892790

2791+
case 'T':
2792+
{
2793+
PyObject *obj = va_arg(*vargs, PyObject *);
2794+
assert(obj);
2795+
PyObject *type_name;
2796+
if (flags & F_ALT) {
2797+
type_name = _PyType_GetFullyQualName(Py_TYPE(obj), 0);
2798+
}
2799+
else {
2800+
type_name = PyType_GetName(Py_TYPE(obj));
2801+
}
2802+
if (!type_name) {
2803+
return NULL;
2804+
}
2805+
if (unicode_fromformat_write_str(writer, type_name,
2806+
width, precision, flags) == -1) {
2807+
Py_DECREF(type_name);
2808+
return NULL;
2809+
}
2810+
Py_DECREF(type_name);
2811+
break;
2812+
}
2813+
27902814
default:
27912815
invalid_format:
27922816
PyErr_Format(PyExc_SystemError, "invalid format string: %s", p);

0 commit comments

Comments
 (0)