Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
20dcb62
gh-119180: Documentation for PEP 649/749
JelleZijlstra Jul 24, 2024
206560c
Document evaluate_forward_ref
JelleZijlstra Jul 24, 2024
edd12a2
Merge remote-tracking branch 'upstream/main' into pep649-doc
JelleZijlstra Jul 24, 2024
79ce456
add something
JelleZijlstra Jul 24, 2024
3f70c21
docs for annotationlib
JelleZijlstra Jul 24, 2024
e297349
Update inspect
JelleZijlstra Jul 24, 2024
d52fb08
Apply suggestions from code review
JelleZijlstra Jul 27, 2024
4e7a2a5
Merge remote-tracking branch 'upstream/main' into pep649-doc
JelleZijlstra Jul 27, 2024
35312e7
More feedback
JelleZijlstra Jul 27, 2024
b30abbd
Docs expansion
JelleZijlstra Jul 27, 2024
bcb6a9f
formatting
JelleZijlstra Jul 27, 2024
c82fb66
Merge remote-tracking branch 'upstream/main' into pep649-doc
JelleZijlstra Jul 27, 2024
b4f6385
More documentation
JelleZijlstra Jul 27, 2024
6ebdce9
Apply suggestions from code review
JelleZijlstra Jul 27, 2024
52dc99e
stray whitespace
JelleZijlstra Jul 27, 2024
61eedbd
Suggestions from Adam on evaluate_forward_ref
JelleZijlstra Jul 27, 2024
27668ce
Expand docs, based on suggestion by Adam
JelleZijlstra Jul 27, 2024
6254e18
Merge remote-tracking branch 'upstream/main' into pep649-doc
JelleZijlstra Sep 9, 2024
c42cb60
doctest?
JelleZijlstra Sep 9, 2024
1873912
expand
JelleZijlstra Sep 10, 2024
86001c1
Feedback from Savannah
JelleZijlstra Sep 10, 2024
c1943fa
fix link
JelleZijlstra Sep 10, 2024
889f5aa
shorter lines
JelleZijlstra Sep 10, 2024
6dd39fb
Apply suggestions from code review
JelleZijlstra Sep 11, 2024
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
14 changes: 9 additions & 5 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,23 @@ Glossary
and loaders (in the :mod:`importlib.abc` module). You can create your own
ABCs with the :mod:`abc` module.

annotate function
A function that can be called to retrieve the :term:`annotations <annotation>`
of an object. This function is accessible as the :attr:`~object.__annotate__`
attribute of functions, classes, and modules.

annotation
A label associated with a variable, a class
attribute or a function parameter or return value,
used by convention as a :term:`type hint`.

Annotations of local variables cannot be accessed at runtime, but
annotations of global variables, class attributes, and functions
are stored in the :attr:`__annotations__`
special attribute of modules, classes, and functions,
respectively.
can be retrieved by calling :func:`annotationlib.get_annotations`
on modules, classes, and functions, respectively.

See :term:`variable annotation`, :term:`function annotation`, :pep:`484`
and :pep:`526`, which describe this functionality.
See :term:`variable annotation`, :term:`function annotation`, :pep:`484`,
:pep:`526` and :pep:`649`, which describe this functionality.
Also see :ref:`annotations-howto`
for best practices on working with annotations.

Expand Down
14 changes: 7 additions & 7 deletions Doc/library/__future__.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ language using this mechanism:
| generator_stop | 3.5.0b1 | 3.7 | :pep:`479`: |
| | | | *StopIteration handling inside generators* |
+------------------+-------------+--------------+---------------------------------------------+
| annotations | 3.7.0b1 | TBD [1]_ | :pep:`563`: |
| | | | *Postponed evaluation of annotations* |
| annotations | 3.7.0b1 | Never [1]_ | :pep:`563`: |
| | | | *Postponed evaluation of annotations*, |
| | | | :pep:`649`: *Deferred evalutation of |
| | | | annotations using descriptors* |
+------------------+-------------+--------------+---------------------------------------------+

.. XXX Adding a new entry? Remember to update simple_stmts.rst, too.
Expand Down Expand Up @@ -115,11 +117,9 @@ language using this mechanism:

.. [1]
``from __future__ import annotations`` was previously scheduled to
become mandatory in Python 3.10, but the Python Steering Council
twice decided to delay the change
(`announcement for Python 3.10 <https://mail.python.org/archives/list/[email protected]/message/CLVXXPQ2T2LQ5MP2Y53VVQFCXYWQJHKZ/>`__;
`announcement for Python 3.11 <https://mail.python.org/archives/list/[email protected]/message/VIZEBX5EYMSYIJNDBF6DMUMZOCWHARSO/>`__).
No final decision has been made yet. See also :pep:`563` and :pep:`649`.
become mandatory in Python 3.10, but the change was delayed and ultimately
canceled. This feature will eventually be deprecated and removed. See
:pep:`649` and :pep:`749`.
Comment on lines 119 to +122
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we provide or link to migration guidance here, especially given that this is the first time a __future__ won't become the actual future?



.. seealso::
Expand Down
178 changes: 178 additions & 0 deletions Doc/library/annotationlib.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
:mod:`!annotationlib` --- Functionality for introspecting annotations
=====================================================================

.. module:: annotationlib
:synopsis: Functionality for introspecting annotations


**Source code:** :source:`Lib/annotationlib.py`

.. testsetup:: default

import annotationlib
from annotationlib import *

--------------

The :mod:`!annotationlib` module provides tools for introspecting :term:`annotations <annotation>`
on modules, classes, and functions.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potentially this could be added later, but I think a longer narrative summary of annotationlib and where it fits in could be of use -- we jump straight into functions here. Contrast with pathlib, heapq, hashlib.

I wonder if there are words from 749 that we could incorporate? Effectively, something that briefly talks to where this module fits into the landscape, perhaps aimed at the fairly experienced user of types/inspect/typing, but someone who hasn't followed the PEPs.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was thinking of adding that too, but ran out of energy while working on the initial version. :) I'll probably work on this later.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote such a summary now, along with a historical account of annotation semantics, since that seems like something the docs should cover.


.. function:: call_annotate_function(annotate, format, *, owner=None)

Call the :term:`annotate function` *annotate* with the given *format*, a member of the :class:`Format`
enum, and return the annotations dictionary produced by the function.

Annotate functions generated by the compiler for functions, classes, and modules support only
the :attr:`~Format.VALUE` format when called directly. This function calls the annotate function
in a special environment that allows it to produce annotations in the other formats.

*owner* is the object that owns the annotation function, usually a function, class, or module.
If provided, it is used in the :attr:`~Format.FORWARDREF` format to produce :class:`ForwardRef`
object that carry more information.

.. versionadded:: 3.14

.. class:: Format

A :class:`enum.IntEnum` describing the formats in which annotations can be returned.
Members of the enum, or their equivalent integer values, can be passed to
:func:`get_annotations` and other functions in this module, as well as to
:attr:`~object.__annotate__` functions.

.. attribute:: VALUE

Values are the result of evaluating the annotation expressions.

This enum member's value is equal to 1.

.. attribute:: FORWARDREF

Values are real annotation values (as per :attr:`Format.VALUE` format) for defined values,
and :class:`ForwardRef` proxies for undefined values. Real objects may be exposed to,
or contain references to, :class:`ForwardRef` proxy objects.

This enum member's value is equal to 2.

.. attribute:: SOURCE

Values are the text string of the annotation as it appears in the source code.
May only be approximate; whitespace may be normalized, and constant values
may be optimized. It is possible the exact values of these strings could
change in future version of Python.

.. versionadded:: 3.14

.. class:: ForwardRef

A proxy object for forward references in annotations.

Instances of this class are returned when the :attr:`~Format.FORWARDREF` format is
used and annotations contain a name that cannot be resolved. This can happen
when a forward reference is used in an annotation, such as when a class is
referenced before it is defined.

.. attribute:: __forward_arg__

A string containing the code that was evaluated to produce the :class:`~ForwardRef`.
The string may not be exactly equivalent to the original source.

.. method:: evaluate(*, globals=None, locals=None, type_params=None, owner=None)

Evaluate the forward reference, returning its value.

This may throw an exception such as :exc:`NameError` if the forward reference
refers to names that do not exist. The parameters to the function can be used to
provide bindings for names that would otherwise be undefined.

*globals* and *locals* are passed to :func:`eval()`, representing the global and
local namespaces in which the name is evaluated. *type_params*, if given, must be
a tuple of :ref:`type parameters <type-params>` that are in scope while the forward
reference is being evaluated. *owner* is the object that owns the annotation from
which the forward reference derives, usually a function, class, or module.
:class:`~ForwardRef` instances returned by :func:`get_annotations` retain
a reference to their owner, so it is not necessary to pass it in explicitly.

Once a :class:`~ForwardRef` instance has been evaluated, it caches the evaluated
value, and future calls to :meth:`evaluate` will return the cached value, regardless
of the parameters passed in.

.. versionadded:: 3.14

.. function:: get_annotate_function(obj)

Retrieve the :term:`annotate function` for *obj*. Return ``None`` if *obj* does not have an
annotate function.

This is usually equivalent to accessing the :attr:`~object.__annotate__` attribute of *obj*,
but direct access to the attribute may return the wrong object in certain situations involving
metaclasses. It is recommended to use this function instead of accessing the attribute directly.

.. versionadded:: 3.14

.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=Format.VALUE)

Compute the annotations dict for an object.

``obj`` may be a callable, class, module, or other object with
:attr:`~object.__annotate__` and :attr:`~object.__annotations__` attributes.
Passing in an object of any other type raises :exc:`TypeError`.

The *format* parameter controls the format in which annotations are returned:

Returns a dict. ``get_annotations()`` returns a new dict every time
it's called; calling it twice on the same object will return two
different but equivalent dicts.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Returns a dict. ``get_annotations()`` returns a new dict every time
it's called; calling it twice on the same object will return two
different but equivalent dicts.
.. note::
This function returns a *new* dict every time it's called; calling it
twice on the same object returns two different but equivalent dicts.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should this be indented into a note? I feel that makes the text noisier.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You'll have a nice box around this (important) behaviour. Since your bullet list contained "Always, always, always creates a new dict", I feel this behaviour should be highlighted. I used "note" but you can also use "important" or "tip".


This function handles several details for you:

* If ``eval_str`` is true, values of type ``str`` will
be un-stringized using :func:`eval()`. This is intended
for use with stringized annotations
(``from __future__ import annotations``). It is an error
to set ``eval_str`` to true with formats other than :attr:`Format.VALUE`.
* If ``obj`` doesn't have an annotations dict, returns an
empty dict. (Functions and methods always have an
annotations dict; classes, modules, and other types of
callables may not.)
* Ignores inherited annotations on classes, as well as annotations
on metaclasses. If a class
doesn't have its own annotations dict, returns an empty dict.
* All accesses to object members and dict values are done
using ``getattr()`` and ``dict.get()`` for safety.
* Always, always, always returns a freshly created dict.

``eval_str`` controls whether or not values of type ``str`` are replaced
with the result of calling :func:`eval()` on those values:

* If eval_str is true, :func:`eval()` is called on values of type ``str``.
(Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()`
raises an exception, it will unwind the stack past the ``get_annotations``
call.)
* If eval_str is false (the default), values of type ``str`` are unchanged.

``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
for :func:`eval()` for more information. If ``globals`` or ``locals``
is ``None``, this function may replace that value with a context-specific
default, contingent on ``type(obj)``:

* If ``obj`` is a module, ``globals`` defaults to ``obj.__dict__``.
* If ``obj`` is a class, ``globals`` defaults to
``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults
to the ``obj`` class namespace.
* If ``obj`` is a callable, ``globals`` defaults to
:attr:`obj.__globals__ <function.__globals__>`,
although if ``obj`` is a wrapped function (using
:func:`functools.update_wrapper`) it is first unwrapped.

Calling :func:`!get_annotations` is best practice for accessing the
annotations dict of any object. See :ref:`annotations-howto` for
more information on annotations best practices.

.. doctest::

>>> def f(a: int, b: str) -> float:
... pass
>>> get_annotations(f)
{'a': <class 'int'>, 'b': <class 'str'>, 'return': <class 'float'>}

.. versionadded:: 3.14
69 changes: 13 additions & 56 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -693,19 +693,19 @@ function.
Accepts a wide range of Python callables, from plain functions and classes to
:func:`functools.partial` objects.

For objects defined in modules using stringized annotations
(``from __future__ import annotations``), :func:`signature` will
If some of the annotations are strings (e.g., because
``from __future__ import annotations`` was used), :func:`signature` will
attempt to automatically un-stringize the annotations using
:func:`get_annotations`. The
:func:`annotationlib.get_annotations`. The
*globals*, *locals*, and *eval_str* parameters are passed
into :func:`get_annotations` when resolving the
annotations; see the documentation for :func:`get_annotations`
into :func:`!annotationlib.get_annotations` when resolving the
annotations; see the documentation for :func:`!annotationlib.get_annotations`
for instructions on how to use these parameters.

Raises :exc:`ValueError` if no signature can be provided, and
:exc:`TypeError` if that type of object is not supported. Also,
if the annotations are stringized, and *eval_str* is not false,
the ``eval()`` call(s) to un-stringize the annotations in :func:`get_annotations`
the ``eval()`` call(s) to un-stringize the annotations in :func:`annotationlib.get_annotations`
could potentially raise any kind of exception.

A slash(/) in the signature of a function denotes that the parameters prior
Expand Down Expand Up @@ -1222,62 +1222,19 @@ Classes and functions
.. versionadded:: 3.4


.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False)
.. function:: get_annotations(obj, *, globals=None, locals=None, eval_str=False, format=annotationlib.Format.VALUE)

Compute the annotations dict for an object.

``obj`` may be a callable, class, or module.
Passing in an object of any other type raises :exc:`TypeError`.

Returns a dict. ``get_annotations()`` returns a new dict every time
it's called; calling it twice on the same object will return two
different but equivalent dicts.

This function handles several details for you:

* If ``eval_str`` is true, values of type ``str`` will
be un-stringized using :func:`eval()`. This is intended
for use with stringized annotations
(``from __future__ import annotations``).
* If ``obj`` doesn't have an annotations dict, returns an
empty dict. (Functions and methods always have an
annotations dict; classes, modules, and other types of
callables may not.)
* Ignores inherited annotations on classes. If a class
doesn't have its own annotations dict, returns an empty dict.
* All accesses to object members and dict values are done
using ``getattr()`` and ``dict.get()`` for safety.
* Always, always, always returns a freshly created dict.

``eval_str`` controls whether or not values of type ``str`` are replaced
with the result of calling :func:`eval()` on those values:

* If eval_str is true, :func:`eval()` is called on values of type ``str``.
(Note that ``get_annotations`` doesn't catch exceptions; if :func:`eval()`
raises an exception, it will unwind the stack past the ``get_annotations``
call.)
* If eval_str is false (the default), values of type ``str`` are unchanged.

``globals`` and ``locals`` are passed in to :func:`eval()`; see the documentation
for :func:`eval()` for more information. If ``globals`` or ``locals``
is ``None``, this function may replace that value with a context-specific
default, contingent on ``type(obj)``:

* If ``obj`` is a module, ``globals`` defaults to ``obj.__dict__``.
* If ``obj`` is a class, ``globals`` defaults to
``sys.modules[obj.__module__].__dict__`` and ``locals`` defaults
to the ``obj`` class namespace.
* If ``obj`` is a callable, ``globals`` defaults to
:attr:`obj.__globals__ <function.__globals__>`,
although if ``obj`` is a wrapped function (using
:func:`functools.update_wrapper`) it is first unwrapped.

Calling ``get_annotations`` is best practice for accessing the
annotations dict of any object. See :ref:`annotations-howto` for
more information on annotations best practices.
This is an alias for :func:`annotationlib.get_annotations`; see the documentation
of that function for more information.

.. versionadded:: 3.10

.. versionchanged:: 3.14
This function is now an alias for :func:`annotationlib.get_annotations`.
Calling it as ``inspect.get_annotations`` will continue to work.


.. _inspect-stack:

Expand Down
1 change: 1 addition & 0 deletions Doc/library/python.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,5 @@ overview:
__future__.rst
gc.rst
inspect.rst
annotationlib.rst
site.rst
29 changes: 29 additions & 0 deletions Doc/library/typing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3235,6 +3235,35 @@ Introspection helpers

.. versionadded:: 3.7.4

.. versionchanged:: 3.14
This is now an alias for :class:`annotationlib.ForwardRef`.

.. function:: evaluate_forward_ref(forward_ref, *, owner=None, globals=None, locals=None, type_params=None, format=annotationlib.Format.VALUE)

Evaluate an :class:`annotationlib.ForwardRef` as a :term:`type hint`.

This is similar to calling the :meth:`annotationlib.ForwardRef.evaluate()` method,
but unlike that method, :func:`!evaluate_forward_ref` also:

* Recursively evaluates forward references nested within the type hint.
* Rejects certain objects that are not valid type hints.
* Replaces type hints that evaluate to None with types.NoneType.
* Supports the :attr:`~annotationlib.Format.FORWARDREF` and
:attr:`~annotationlib.Format.SOURCE` formats.

*forward_ref* must be an instance of :class:`~annotationlib.ForwardRef`. *owner*, if given,
should be the object that holds the annotations that the forward reference
derived from, such as a module, class object, or function. It is used to
infer the namespaces to use for looking up names. *globals* and *locals*
can also be explicitly given to provide the global and local namespaces.
*type_params* is a tuple of :ref:`type parameters <type-params>` that are in scope when
evaluating the forward reference. This parameter must be provided (though
it may be an empty tuple) if *owner* is not given and the forward reference
does not already have an owner set. *format* specifies the format of the
annotation and is a member of the :class:`annotationlib.Format` enum.

.. versionadded:: 3.14

.. data:: NoDefault

A sentinel object used to indicate that a type parameter has no default
Expand Down
Loading
Loading