Skip to content

Commit 54b9e5b

Browse files
committed
pythongh-111696: Add %T format to PyUnicode_FromFormat()
* Add "%T" and "%#T" formats to PyUnicode_FromFormat(). * Add "T" and "#T" formats to type.__format__(). * Add type.__fullyqualname__ read-only attribute.
1 parent 2bc01cc commit 54b9e5b

File tree

13 files changed

+292
-24
lines changed

13 files changed

+292
-24
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 fully qualified name of the object type
526+
(:attr:`class.__fullyqualname__` attribute): the result of calling
527+
``PyObject_GetAttrString(Py_TYPE(obj), "__fullyqualname__")``.
528+
529+
* - ``T#``
530+
- :c:expr:`PyObject*`
531+
- Get the name of the object type (``type.__name__``): the result of calling
532+
``PyType_GetName(Py_TYPE(obj))``.
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 :term:`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 fully qualified name |
597+
| | (:attr:`class.__qualname__`). |
598+
+----------+----------------------------------------------------------+
599+
| ``'#T'`` | Format the type name (``type.__name__``). |
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: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,12 @@ 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 ``T`` and ``#T`` formats to :func:`type.__format__`: ``T`` formats the
129+
fully qualified type name (:attr:`class.__fullyqualname__` attribute) and
130+
``#T`` formats the type name (``type.__name__``).
131+
(Contributed by Victor Stinner in :gh:`111696`.)
132+
133+
128134
New Modules
129135
===========
130136

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

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

11311143
Porting to Python 3.13
11321144
----------------------

Include/internal/pycore_typeobject.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ extern "C" {
99
#endif
1010

1111
#include "pycore_moduleobject.h" // PyModuleObject
12+
#include "pycore_unicodeobject.h" // _PyUnicodeWriter
1213

1314

1415
/* state */
@@ -143,6 +144,17 @@ extern PyTypeObject _PyBufferWrapper_Type;
143144
extern PyObject* _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
144145
PyObject *name, int *meth_found);
145146

147+
extern PyObject* _PyType_GetFullyQualName(PyTypeObject *type);
148+
149+
// Format the type based on the format_spec, as defined in PEP 3101
150+
// (Advanced String Formatting).
151+
extern int _PyType_FormatAdvancedWriter(
152+
_PyUnicodeWriter *writer,
153+
PyTypeObject *obj,
154+
PyObject *format_spec,
155+
Py_ssize_t start,
156+
Py_ssize_t end);
157+
146158
#ifdef __cplusplus
147159
}
148160
#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+
check_format(f'type: {LocalType.__module__}.{LocalType.__qualname__}',
596+
b'type: %T', py_object(obj))
597+
name = 'LocalType'
598+
check_format(f'type: {name}',
599+
b'type: %#T', py_object(obj))
600+
check_format(f'type: {name[:3]}',
601+
b'type: %#.3T', py_object(obj))
602+
check_format(f'type: {"LocalType".rjust(20)}',
603+
b'type: %#20T', 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'))

Lib/test/test_format.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -622,6 +622,18 @@ def test_specifier_z_error(self):
622622
with self.assertRaisesRegex(ValueError, error_msg):
623623
b"%z.1f" % 0
624624

625+
def test_type_name(self):
626+
class LocalClass:
627+
pass
628+
obj = LocalClass()
629+
self.assertEqual(f"{type(obj):T}",
630+
f"{LocalClass.__module__}.{LocalClass.__qualname__}")
631+
self.assertEqual(f"{type(obj):#T}", "LocalClass")
632+
633+
obj = "abc"
634+
self.assertEqual(f"{type(obj):T}", "str")
635+
self.assertEqual(f"{type(obj):#T}", "str")
636+
625637

626638
if __name__ == "__main__":
627639
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Add support for ``"%T"`` format to :c:func:`PyUnicode_FromFormat`: format
2+
the qualifed name of an object. Patch by Victor Stinner.

Objects/clinic/typeobject.c.h

Lines changed: 30 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)