Skip to content

Commit

Permalink
pythongh-111696: Add %T format to PyUnicode_FromFormat()
Browse files Browse the repository at this point in the history
* Add "%T" and "%#T" formats to PyUnicode_FromFormat().
* Add "T" and "#T" formats to type.__format__().
* Add type.__fullyqualname__ read-only attribute.
  • Loading branch information
vstinner committed Nov 3, 2023
1 parent 2bc01cc commit 54b9e5b
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 24 deletions.
16 changes: 16 additions & 0 deletions Doc/c-api/unicode.rst
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,8 @@ APIs:
| ``-`` | The converted value is left adjusted (overrides the ``0`` |
| | flag if both are given). |
+-------+-------------------------------------------------------------+
| ``#`` | Alternate form |
+-------+-------------------------------------------------------------+
The length modifiers for following integer conversions (``d``, ``i``,
``o``, ``u``, ``x``, or ``X``) specify the type of the argument
Expand Down Expand Up @@ -518,6 +520,17 @@ APIs:
- :c:expr:`PyObject*`
- The result of calling :c:func:`PyObject_Repr`.
* - ``T``
- :c:expr:`PyObject*`
- Get the fully qualified name of the object type
(:attr:`class.__fullyqualname__` attribute): the result of calling
``PyObject_GetAttrString(Py_TYPE(obj), "__fullyqualname__")``.
* - ``T#``
- :c:expr:`PyObject*`
- Get the name of the object type (``type.__name__``): the result of calling
``PyType_GetName(Py_TYPE(obj))``.
.. 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`
Expand Down Expand Up @@ -553,6 +566,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"`` and ``"%#T"`` added.
.. c:function:: PyObject* PyUnicode_FromFormatV(const char *format, va_list vargs)
Expand Down
10 changes: 10 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5493,6 +5493,16 @@ types, where they are relevant. Some of these are not reported by the
.. versionadded:: 3.3


.. attribute:: class.__fullyqualname__

The fully :term:`qualified name` of the class instance:
``f"{class.__module__}.{class.__qualname__}"``, or
``f"{class.__qualname__}"`` if ``class.__module__`` is equal to
``"builtins"``.

.. versionadded:: 3.13


.. attribute:: definition.__type_params__

The :ref:`type parameters <type-params>` of generic classes, functions,
Expand Down
14 changes: 14 additions & 0 deletions Doc/library/string.rst
Original file line number Diff line number Diff line change
Expand Up @@ -588,6 +588,20 @@ The available presentation types for :class:`float` and
| | as altered by the other format modifiers. |
+---------+----------------------------------------------------------+

The available presentation types for :class:`type` values are:

+----------+----------------------------------------------------------+
| Type | Meaning |
+==========+==========================================================+
| ``'T'`` | Format the type fully qualified name |
| | (:attr:`class.__qualname__`). |
+----------+----------------------------------------------------------+
| ``'#T'`` | Format the type name (``type.__name__``). |
+----------+----------------------------------------------------------+

.. versionchanged:: 3.13
Add ``T`` and ``T#`` formats for :class:`type` values.


.. _formatexamples:

Expand Down
12 changes: 12 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ Other Language Changes
equivalent of the :option:`-X frozen_modules <-X>` command-line option.
(Contributed by Yilei Yang in :gh:`111374`.)

* Add ``T`` and ``#T`` formats to :func:`type.__format__`: ``T`` formats the
fully qualified type name (:attr:`class.__fullyqualname__` attribute) and
``#T`` formats the type name (``type.__name__``).
(Contributed by Victor Stinner in :gh:`111696`.)


New Modules
===========

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

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


Porting to Python 3.13
----------------------
Expand Down
12 changes: 12 additions & 0 deletions Include/internal/pycore_typeobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ extern "C" {
#endif

#include "pycore_moduleobject.h" // PyModuleObject
#include "pycore_unicodeobject.h" // _PyUnicodeWriter


/* state */
Expand Down Expand Up @@ -143,6 +144,17 @@ extern PyTypeObject _PyBufferWrapper_Type;
extern PyObject* _PySuper_Lookup(PyTypeObject *su_type, PyObject *su_obj,
PyObject *name, int *meth_found);

extern PyObject* _PyType_GetFullyQualName(PyTypeObject *type);

// Format the type based on the format_spec, as defined in PEP 3101
// (Advanced String Formatting).
extern int _PyType_FormatAdvancedWriter(
_PyUnicodeWriter *writer,
PyTypeObject *obj,
PyObject *format_spec,
Py_ssize_t start,
Py_ssize_t end);

#ifdef __cplusplus
}
#endif
Expand Down
6 changes: 6 additions & 0 deletions Lib/test/test_builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2430,6 +2430,7 @@ def test_new_type(self):
self.assertEqual(A.__name__, 'A')
self.assertEqual(A.__qualname__, 'A')
self.assertEqual(A.__module__, __name__)
self.assertEqual(A.__fullyqualname__, f'{__name__}.A')
self.assertEqual(A.__bases__, (object,))
self.assertIs(A.__base__, object)
x = A()
Expand All @@ -2443,6 +2444,7 @@ def ham(self):
self.assertEqual(C.__name__, 'C')
self.assertEqual(C.__qualname__, 'C')
self.assertEqual(C.__module__, __name__)
self.assertEqual(C.__fullyqualname__, f'{__name__}.C')
self.assertEqual(C.__bases__, (B, int))
self.assertIs(C.__base__, int)
self.assertIn('spam', C.__dict__)
Expand All @@ -2468,6 +2470,7 @@ def test_type_name(self):
self.assertEqual(A.__name__, name)
self.assertEqual(A.__qualname__, name)
self.assertEqual(A.__module__, __name__)
self.assertEqual(A.__fullyqualname__, f'{__name__}.{name}')
with self.assertRaises(ValueError):
type('A\x00B', (), {})
with self.assertRaises(UnicodeEncodeError):
Expand All @@ -2482,6 +2485,7 @@ def test_type_name(self):
self.assertEqual(C.__name__, name)
self.assertEqual(C.__qualname__, 'C')
self.assertEqual(C.__module__, __name__)
self.assertEqual(C.__fullyqualname__, f'{__name__}.C')

A = type('C', (), {})
with self.assertRaises(ValueError):
Expand All @@ -2499,13 +2503,15 @@ def test_type_qualname(self):
self.assertEqual(A.__name__, 'A')
self.assertEqual(A.__qualname__, 'B.C')
self.assertEqual(A.__module__, __name__)
self.assertEqual(A.__fullyqualname__, f'{__name__}.B.C')
with self.assertRaises(TypeError):
type('A', (), {'__qualname__': b'B'})
self.assertEqual(A.__qualname__, 'B.C')

A.__qualname__ = 'D.E'
self.assertEqual(A.__name__, 'A')
self.assertEqual(A.__qualname__, 'D.E')
self.assertEqual(A.__fullyqualname__, f'{__name__}.D.E')
with self.assertRaises(TypeError):
A.__qualname__ = b'B'
self.assertEqual(A.__qualname__, 'D.E')
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_capi/test_unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,24 @@ def check_format(expected, format, *args):
check_format('xyz',
b'%V', None, b'xyz')

# test %T and %#T
check_format('type: str',
b'type: %T', 'abc')
check_format('type: str',
b'type: %#T', 'abc')
class LocalType:
pass
obj = LocalType()
check_format(f'type: {LocalType.__module__}.{LocalType.__qualname__}',
b'type: %T', py_object(obj))
name = 'LocalType'
check_format(f'type: {name}',
b'type: %#T', py_object(obj))
check_format(f'type: {name[:3]}',
b'type: %#.3T', py_object(obj))
check_format(f'type: {"LocalType".rjust(20)}',
b'type: %#20T', py_object(obj))

# test %ls
check_format('abc', b'%ls', c_wchar_p('abc'))
check_format('\u4eba\u6c11', b'%ls', c_wchar_p('\u4eba\u6c11'))
Expand Down
12 changes: 12 additions & 0 deletions Lib/test/test_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -622,6 +622,18 @@ def test_specifier_z_error(self):
with self.assertRaisesRegex(ValueError, error_msg):
b"%z.1f" % 0

def test_type_name(self):
class LocalClass:
pass
obj = LocalClass()
self.assertEqual(f"{type(obj):T}",
f"{LocalClass.__module__}.{LocalClass.__qualname__}")
self.assertEqual(f"{type(obj):#T}", "LocalClass")

obj = "abc"
self.assertEqual(f"{type(obj):T}", "str")
self.assertEqual(f"{type(obj):#T}", "str")


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add support for ``"%T"`` format to :c:func:`PyUnicode_FromFormat`: format
the qualifed name of an object. Patch by Victor Stinner.
31 changes: 30 additions & 1 deletion Objects/clinic/typeobject.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 54b9e5b

Please sign in to comment.