Skip to content

PEP 737: gh-111696: Add type.__fully_qualified_name__ attribute #112133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Doc/c-api/type.rst
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,14 @@ Type Objects

.. versionadded:: 3.11

.. c:function:: PyObject* PyType_GetFullyQualifiedName(PyTypeObject *type)

Return the type's :term:`fully qualified name`. Equivalent to getting the
type's :attr:`__fully_qualified_name__ <class.__fully_qualified_name__>`
attribute.

.. versionadded:: 3.13

.. c:function:: void* PyType_GetSlot(PyTypeObject *type, int slot)

Return the function pointer stored in the given slot. If the
Expand Down
19 changes: 19 additions & 0 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,25 @@ Glossary
division. Note that ``(-11) // 4`` is ``-3`` because that is ``-2.75``
rounded *downward*. See :pep:`238`.

fully qualified name
The fully qualified name is the entire dotted path to a class or a
module.

The :attr:`class.__fully_qualified_name__` attribute includes the module
name, except for built-in classes. Example::

>>> import collections
>>> collections.OrderedDict.__fully_qualified_name__
'collections.OrderedDict'

When used to refer to modules, the *fully qualified name* means the
entire dotted path to the module, including any parent packages,
e.g. ``email.mime.text``::

>>> import email.mime.text
>>> email.mime.text.__name__
'email.mime.text'

function
A series of statements which returns some value to a caller. It can also
be passed zero or more :term:`arguments <argument>` which may be used in
Expand Down
6 changes: 3 additions & 3 deletions Doc/library/doctest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -587,13 +587,13 @@ doctest decides whether actual output matches an example's expected output:
.. data:: IGNORE_EXCEPTION_DETAIL

When specified, doctests expecting exceptions pass so long as an exception
of the expected type is raised, even if the details
(message and fully qualified exception name) don't match.
of the expected type is raised, even if the details (message and
:term:`fully qualified exception name <fully qualified name>`) don't match.

For example, an example expecting ``ValueError: 42`` will pass if the actual
exception raised is ``ValueError: 3*14``, but will fail if, say, a
:exc:`TypeError` is raised instead.
It will also ignore any fully qualified name included before the
It will also ignore any :term:`fully qualified name` included before the
exception class, which can vary between implementations and versions
of Python and the code/libraries in use.
Hence, all three of these variations will work with the flag specified:
Expand Down
4 changes: 2 additions & 2 deletions Doc/library/email.contentmanager.rst
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@
found:

* the type itself (``typ``)
* the type's fully qualified name (``typ.__module__ + '.' +
typ.__qualname__``).
* the type's :term:`fully qualified name`
(:attr:`typ.__fully_qualified_name__ <class.__fully_qualified_name__>`).
* the type's qualname (``typ.__qualname__``)
* the type's name (``typ.__name__``).

Expand Down
12 changes: 6 additions & 6 deletions Doc/library/importlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ ABC hierarchy::
reloaded):

- :attr:`__name__`
The module's fully qualified name.
The module's :term:`fully qualified name`.
It is ``'__main__'`` for an executed module.

- :attr:`__file__`
Expand All @@ -377,8 +377,8 @@ ABC hierarchy::
as an indicator that the module is a package.

- :attr:`__package__`
The fully qualified name of the package the module is in (or the
empty string for a top-level module).
The :term:`fully qualified name` of the package the module is in
(or the empty string for a top-level module).
If the module is a package then this is the same as :attr:`__name__`.

- :attr:`__loader__`
Expand Down Expand Up @@ -1181,7 +1181,7 @@ find and load modules.

(:attr:`__name__`)

The module's fully qualified name.
The module's :term:`fully qualified name`.
The :term:`finder` should always set this attribute to a non-empty string.

.. attribute:: loader
Expand Down Expand Up @@ -1230,8 +1230,8 @@ find and load modules.

(:attr:`__package__`)

(Read-only) The fully qualified name of the package the module is in (or the
empty string for a top-level module).
(Read-only) The :term:`fully qualified name` of the package the module is in
(or the empty string for a top-level module).
If the module is a package then this is the same as :attr:`name`.

.. attribute:: has_location
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
| | co_name | name with which this code |
| | | object was defined |
+-----------+-------------------+---------------------------+
| | co_qualname | fully qualified name with |
| | co_qualname | qualified name with |
| | | which this code object |
| | | was defined |
+-----------+-------------------+---------------------------+
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/logging.config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,7 @@ otherwise, the context is used to determine what to instantiate.

The configuring dict is searched for the following keys:

* ``class`` (mandatory). This is the fully qualified name of the
* ``class`` (mandatory). This is the :term:`fully qualified name` of the
handler class.

* ``level`` (optional). The level of the handler.
Expand Down
2 changes: 1 addition & 1 deletion Doc/library/pickle.rst
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ the function's code, nor any of its function attributes are pickled. Thus the
defining module must be importable in the unpickling environment, and the module
must contain the named object, otherwise an exception will be raised. [#]_

Similarly, classes are pickled by fully qualified name, so the same restrictions in
Similarly, classes are pickled by :term:`fully qualified name`, so the same restrictions in
the unpickling environment apply. Note that none of the class's code or data is
pickled, so in the following example the class attribute ``attr`` is not
restored in the unpickling environment::
Expand Down
9 changes: 9 additions & 0 deletions Doc/library/stdtypes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5496,6 +5496,15 @@ types, where they are relevant. Some of these are not reported by the
.. versionadded:: 3.3


.. attribute:: class.__fully_qualified_name__

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

.. versionadded:: 3.13


.. attribute:: definition.__type_params__

The :ref:`type parameters <type-params>` of generic classes, functions,
Expand Down
4 changes: 2 additions & 2 deletions Doc/library/unittest.rst
Original file line number Diff line number Diff line change
Expand Up @@ -234,8 +234,8 @@ Command-line options
test name using :meth:`fnmatch.fnmatchcase`; otherwise simple case-sensitive
substring matching is used.

Patterns are matched against the fully qualified test method name as
imported by the test loader.
Patterns are matched against the :term:`fully qualified test method name
<fully qualified name>` as imported by the test loader.

For example, ``-k foo`` matches ``foo_tests.SomeTest.test_something``,
``bar_tests.SomeTest.test_foo``, but not ``bar_tests.FooTest.test_something``.
Expand Down
3 changes: 2 additions & 1 deletion Doc/library/warnings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,8 @@ the disposition of the match. Each entry is a tuple of the form (*action*,
category must be a subclass in order to match.

* *module* is a string containing a regular expression that the start of the
fully qualified module name must match, case-sensitively. In :option:`-W` and
:term:`fully qualified module name <fully qualified name>` must match,
case-sensitively. In :option:`-W` and
:envvar:`PYTHONWARNINGS`, *module* is a literal string that the
fully qualified module name must be equal to (case-sensitively), ignoring any
whitespace at the start or end of *module*.
Expand Down
2 changes: 1 addition & 1 deletion Doc/reference/datamodel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1076,7 +1076,7 @@ indirectly) to mutable objects.
single: co_qualname (code object attribute)

Special read-only attributes: :attr:`co_name` gives the function name;
:attr:`co_qualname` gives the fully qualified function name;
:attr:`co_qualname` gives the qualified function name;
:attr:`co_argcount` is the total number of positional arguments
(including positional-only arguments and arguments with default values);
:attr:`co_posonlyargcount` is the number of positional-only arguments
Expand Down
10 changes: 5 additions & 5 deletions Doc/reference/import.rst
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,8 @@ See also :pep:`420` for the namespace package specification.
Searching
=========

To begin the search, Python needs the :term:`fully qualified <qualified name>`
name of the module (or package, but for the purposes of this discussion, the
To begin the search, Python needs the :term:`fully qualified name`
of the module (or package, but for the purposes of this discussion, the
difference is immaterial) being imported. This name may come from various
arguments to the :keyword:`import` statement, or from the parameters to the
:func:`importlib.import_module` or :func:`__import__` functions.
Expand Down Expand Up @@ -547,7 +547,7 @@ listed below.

.. attribute:: __name__

The ``__name__`` attribute must be set to the fully qualified name of
The ``__name__`` attribute must be set to the :term:`fully qualified name` of
the module. This name is used to uniquely identify the module in
the import system.

Expand Down Expand Up @@ -885,7 +885,7 @@ contribute portions to namespace packages, path entry finders must implement
the :meth:`~importlib.abc.PathEntryFinder.find_spec` method.

:meth:`~importlib.abc.PathEntryFinder.find_spec` takes two arguments: the
fully qualified name of the module being imported, and the (optional) target
:term:`fully qualified name` of the module being imported, and the (optional) target
module. ``find_spec()`` returns a fully populated spec for the module.
This spec will always have "loader" set (with one exception).

Expand All @@ -905,7 +905,7 @@ a list containing the portion.
implemented on the path entry finder, the legacy methods are ignored.

:meth:`!find_loader` takes one argument, the
fully qualified name of the module being imported. ``find_loader()``
:term:`fully qualified name` of the module being imported. ``find_loader()``
returns a 2-tuple where the first item is the loader and the second item
is a namespace :term:`portion`.

Expand Down
4 changes: 2 additions & 2 deletions Doc/using/cmdline.rst
Original file line number Diff line number Diff line change
Expand Up @@ -464,8 +464,8 @@ Miscellaneous options
whether the actual warning category of the message is a subclass of the
specified warning category.

The *module* field matches the (fully qualified) module name; this match is
case-sensitive.
The *module* field matches the :term:`fully qualified module name <fully
qualified name>`; this match is case-sensitive.

The *lineno* field matches the line number, where zero matches all line
numbers and is thus equivalent to an omitted line number.
Expand Down
10 changes: 10 additions & 0 deletions Doc/whatsnew/3.13.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ Other Language Changes
equivalent of the :option:`-X frozen_modules <-X>` command-line option.
(Contributed by Yilei Yang in :gh:`111374`.)

* Add the :attr:`__fully_qualified_name__ <class.__fully_qualified_name__>`
read-only attribute to types: the :term:`fully qualified name` of the type.
(Contributed by Victor Stinner in :gh:`111696`.)


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

Expand Down Expand Up @@ -1181,6 +1186,11 @@ New Features
:exc:`KeyError` if the key missing.
(Contributed by Stefan Behnel and Victor Stinner in :gh:`111262`.)

* Add :c:func:`PyType_GetFullyQualifiedName` function: get the type's
:term:`fully qualified name`. It is equivalent to getting the type's
:attr:`__fully_qualified_name__ <class.__fully_qualified_name__>` attribute.
(Contributed by Victor Stinner in :gh:`111696`.)


Porting to Python 3.13
----------------------
Expand Down
1 change: 1 addition & 0 deletions Include/cpython/object.h
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ PyAPI_FUNC(const char *) _PyType_Name(PyTypeObject *);
PyAPI_FUNC(PyObject *) _PyType_Lookup(PyTypeObject *, PyObject *);
PyAPI_FUNC(PyObject *) PyType_GetModuleByDef(PyTypeObject *, PyModuleDef *);
PyAPI_FUNC(PyObject *) PyType_GetDict(PyTypeObject *);
PyAPI_FUNC(PyObject *) PyType_GetFullyQualifiedName(PyTypeObject *);

PyAPI_FUNC(int) PyObject_Print(PyObject *, FILE *, int);
PyAPI_FUNC(void) _Py_BreakPoint(void);
Expand Down
4 changes: 1 addition & 3 deletions Lib/_collections_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -528,9 +528,7 @@ def _type_repr(obj):
(Keep this roughly in sync with the typing version.)
"""
if isinstance(obj, type):
if obj.__module__ == 'builtins':
return obj.__qualname__
return f'{obj.__module__}.{obj.__qualname__}'
return obj.__fully_qualified_name__
if obj is Ellipsis:
return '...'
if isinstance(obj, FunctionType):
Expand Down
2 changes: 1 addition & 1 deletion Lib/_py_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def register(cls, subclass):

def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Class: {cls.__fully_qualified_name__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
for name in cls.__dict__:
if name.startswith("_abc_"):
Expand Down
2 changes: 1 addition & 1 deletion Lib/abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def __subclasscheck__(cls, subclass):

def _dump_registry(cls, file=None):
"""Debug helper to print the ABC registry."""
print(f"Class: {cls.__module__}.{cls.__qualname__}", file=file)
print(f"Class: {cls.__fully_qualified_name__}", file=file)
print(f"Inv. counter: {get_cache_token()}", file=file)
(_abc_registry, _abc_cache, _abc_negative_cache,
_abc_negative_cache_version) = _get_dump(cls)
Expand Down
4 changes: 2 additions & 2 deletions Lib/codecs.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def __new__(cls, encode, decode, streamreader=None, streamwriter=None,
return self

def __repr__(self):
return "<%s.%s object for encoding %s at %#x>" % \
(self.__class__.__module__, self.__class__.__qualname__,
return "<%s object for encoding %s at %#x>" % \
(self.__class__.__fully_qualified_name__,
self.name, id(self))

def __getnewargs__(self):
Expand Down
4 changes: 2 additions & 2 deletions Lib/contextlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def enter_context(self, cm):
_enter = cls.__enter__
_exit = cls.__exit__
except AttributeError:
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
raise TypeError(f"'{cls.__fully_qualified_name__}' object does "
f"not support the context manager protocol") from None
result = _enter(cm)
self._push_cm_exit(cm, _exit)
Expand Down Expand Up @@ -662,7 +662,7 @@ async def enter_async_context(self, cm):
_enter = cls.__aenter__
_exit = cls.__aexit__
except AttributeError:
raise TypeError(f"'{cls.__module__}.{cls.__qualname__}' object does "
raise TypeError(f"'{cls.__fully_qualified_name__}' object does "
f"not support the asynchronous context manager protocol"
) from None
result = await _enter(cm)
Expand Down
2 changes: 1 addition & 1 deletion Lib/doctest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1401,7 +1401,7 @@ def __run(self, test, compileflags, out):
# They start with `SyntaxError:` (or any other class name)
exception_line_prefixes = (
f"{exception[0].__qualname__}:",
f"{exception[0].__module__}.{exception[0].__qualname__}:",
f"{exception[0].__fully_qualified_name__}:",
)
exc_msg_index = next(
index
Expand Down
4 changes: 2 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1501,9 +1501,9 @@ def repl(match):
if isinstance(annotation, types.GenericAlias):
return str(annotation)
if isinstance(annotation, type):
if annotation.__module__ in ('builtins', base_module):
if annotation.__module__ == base_module:
return annotation.__qualname__
return annotation.__module__+'.'+annotation.__qualname__
return annotation.__fully_qualified_name__
return repr(annotation)

def formatannotationrelativeto(object):
Expand Down
2 changes: 1 addition & 1 deletion Lib/multiprocessing/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,7 @@ def __del__(self, _warn=warnings.warn, RUN=RUN):

def __repr__(self):
cls = self.__class__
return (f'<{cls.__module__}.{cls.__qualname__} '
return (f'<{cls.__fully_qualified_name__} '
f'state={self._state} '
f'pool_size={len(self._pool)}>')

Expand Down
2 changes: 1 addition & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1726,7 +1726,7 @@ def do_whatis(self, arg):
return
# Is it a class?
if value.__class__ is type:
self.message('Class %s.%s' % (value.__module__, value.__qualname__))
self.message(f'Class {value.__fully_qualified_name__}')
return
# None of the above...
self.message(type(value))
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/support/asyncore.py
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@ def __init__(self, sock=None, map=None):
self.socket = None

def __repr__(self):
status = [self.__class__.__module__+"."+self.__class__.__qualname__]
status = [self.__class__.__fully_qualified_name__]
if self.accepting and self.addr:
status.append('listening')
elif self.connected:
Expand Down
Loading