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_name
member. - 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,
%.200s
format 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