Hi,
tl; dr: I propose:
- Python: Add
type.__fullyqualname__read-only attribute:type.__module__ + '.' + type.__qualname__, ortype.__qualname__istype.__module__is equal to"builtins". - C API: Add
%T(type(obj).__name__)and%#T(type(obj).__fullyqualname__)formats toPyUnicode_FromFormat(), and so toPyErr_Format()
What do you think of that?
In C, it’s common to format a type name with code like:
PyErr_Format(PyExc_TypeError,
"__format__ must return a str, not %.200s",
Py_TYPE(result)->tp_name);
This code has multiple issues:
- It cannot be used with the limited C API which cannot access
PyTypeObject.tp_namemember. - It’s inefficient: tp_name is a UTF-8 bytes string, it must be decoded at each call to create the Unicode error message.
- (Minor issue?)
Py_TYPE()returns a borrowed reference. In more complicated code, the pointer can become a dangling pointer before the type name is formatting and so we may or may not crash. - By the way,
%.200sformat to truncate the name to 200 characters comes from an old (fixed) limitation of CPython which used buffer of fixed size (ex: 500 bytes). IMO it’s bad to truncate a name without indicating that the string is truncated. Moreover, we don’t do that in Python, so we should not do it in C: code written in C should have the same behavior then Python code (see PEP 399 which is related).
I propose adding %T format to PyUnicode_FromFormat(): format the object type name: similar to type(obj).__name__ in Python. The example becomes just:
PyErr_Format(PyExc_TypeError,
"__format__ must return a str, not %T", result);
Simpler, safer, faster and shorter code!
Note: my implementation supports %.200T format is you really love truncating type names ![]()
In some cases, we might want to display more information about the type: the module where the type was defined and the qualified name. I propose to add also %#T format to get type.__module__ + '.' + type.__qualname__, or just type.__qualname__ is type.__module__ is equal to "builtins".
It’s bad to add an API only accessible in C, so I also propose adding a read-only type.__fullyqualname__ attribute which formats the type name the same way: similar to repr(type) without <class ' prefix and '> suffix ![]()
I’m not sure about the name, in the past, type.__fqn__ was proposed, but this accronym doesn’t fit with other type attributes: none of them are acronyms. Such short name might sound cryptic.
The opposite would be a fully expanded name: type.__fullyqualifiedname__ (qualified instead of qual). ![]()
I’m not fully convinced that formatting a “fully qualified type name” is needed. In general, it’s rare to define two types with the same name in a project. But it may be helpful to distinguish two types with the same “short name” (type.__name__).
There is already type.__qualname__. Maybe the C format #T should use this one instead?
Note: in C, %t is now used by ptrdiff_t type (ex: ptrdiff x = 1; printf("x=%td\n", x);).
Some people also asked to add a similar API to format a type name in Python, but I’m not sure about that.
In Python, it’s easy and reliable to get an object type: type(obj) (or obj.__class__). It’s rare (and a bad idea) to override built-in type() function in a function (and if you do it, you may have other issues).
Moreover, formatting a type name in Python is also easy and straightforward: type.__name__. Done!
Full examples (extracted from the stdlib):
raise TypeError('expected AST, got %r' % node.__class__.__name__)
raise TypeError("key: expected bytes or bytearray, but got %r" % type(key).__name__)
Still, some people asked to “a new API” to format a type name. Well, it would be possible to add T and #T formats to type.__format__. Example:
raise TypeError(f'expected AST, got {node.__class__:T}')
raise TypeError(f"key: expected bytes or bytearray, but got {type(key):T}") # remove quotes
I’m not convinced that “a magic T format” is better or more explicit than the short and straightforward type.__name__ code.
For the “fully qualified name”, you will be able to write:
raise TypeError(f'expected AST, got {node.__class__.__fullyqualname__}')
raise TypeError(f"key: expected bytes or bytearray, but got {type(key).__fullyqualname__}")
For me, the main problem of adding a new API to Python is that the proposed API for C expects an object, whereas here in Python I’m proposing a new API for types (type(obj)). It can be surprising or be error-prone to have a similar API (T format) in C and Python, but expect a different argument (object vs type).
It was proposed to add !t formatter to get the type of an object, but Eric Smith was against this. As I wrote, getting an object type is simple in Python, especially in f-string.
See also:
- New Issue: PyUnicode_FromFormat(): Add %T format to format the type name of an object · Issue #111696 · python/cpython · GitHub
- My previous issue in 2018: PyUnicode_FromFormat(): add %T format for an object type name · Issue #78776 · python/cpython · GitHub
- python-dev discussion in 2018: Mailman 3 bpo-34595: How to format a type name? - Python-Dev - python.org