Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 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
20 changes: 15 additions & 5 deletions Doc/glossary.rst
Original file line number Diff line number Diff line change
Expand Up @@ -36,19 +36,24 @@ 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. Annotate functions are a
subset of :term:`evaluate functions <evaluate function>`.

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 Expand Up @@ -366,6 +371,11 @@ Glossary
statements. The technique contrasts with the :term:`LBYL` style
common to many other languages such as C.

evaluate function
A function that can be called to evaluate a lazily evaluated attribute
of an object, such as the value of type aliases created with the :keyword:`type`
statement.

expression
A piece of syntax which can be evaluated to some value. In other words,
an expression is an accumulation of expression elements like literals,
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
231 changes: 231 additions & 0 deletions Doc/library/annotationlib.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
: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.

This helper function is required because annotate functions generated by
the compiler for functions, classes, and modules only support
the :attr:`~Format.VALUE` format when called directly.
To support other formats, this function calls the annotate function
in a special environment that allows it to produce annotations in the other formats.
This is a useful building block when implementing functionality
that needs to partially evaluate annotations while a class is being constructed.

*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 a :class:`ForwardRef` object that carries more information.

.. seealso::

:PEP:`PEP 649 <649#the-stringizer-and-the-fake-globals-environment>` contains
an explanation of the implementation technique used by this function.

.. versionadded:: 3.14

.. function:: call_evaluate_function(evaluate, format, *, owner=None)

Call the :term:`evaluate function` *evaluate* with the given *format*, a member of the :class:`Format`
enum, and return the value produced by the function. This is similar to :func:`call_annotate_function`,
but the latter always returns a dictionary mapping strings to annotations, while this function returns
a single value.

This is intended for use with the evaluate functions generated for lazily evaluated elements
related to type aliases and type parameters:

* :meth:`typing.TypeAliasType.evaluate_value`, the value of type aliases
* :meth:`typing.TypeVar.evaluate_bound`, the bound of type variables
* :meth:`typing.TypeVar.evaluate_constraints`, the constraints of type variables
* :meth:`typing.TypeVar.evaluate_default`, the default value of type variables
* :meth:`typing.ParamSpec.evaluate_default`, the default value of parameter specifications
* :meth:`typing.TypeVarTuple.evaluate_default`, the default value of type variable tuples

*owner* is the object that owns the evaluate function, such as the type alias or type variable object.

*format* can be used to control the format in which the value is returned:

.. doctest::

>>> type Alias = undefined
>>> call_evaluate_function(Alias.evaluate_value, Format.VALUE)
Traceback (most recent call last):
...
NameError: name 'undefined' is not defined
>>> call_evaluate_function(Alias.evaluate_value, Format.FORWARDREF)
ForwardRef('undefined')
>>> call_evaluate_function(Alias.evaluate_value, Format.SOURCE)
'undefined'

.. versionadded:: 3.14

.. class:: Format

An :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
:value: 1

Values are the result of evaluating the annotation expressions.

.. attribute:: FORWARDREF
:value: 2

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

.. attribute:: SOURCE
:value: 3

Values are the text string of the annotation as it appears in the source code,
up to modifications including, but not restricted to, whitespace normalizations
and constant values optimizations.

The exact values of these strings may change in future versions 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 arguments to this method can be used to
provide bindings for names that would otherwise be undefined.

:class:`~ForwardRef` instances returned by :func:`get_annotations` retain
references to information about the scope they originated from, so calling
this method with no further arguments may be sufficient to evaluate such objects.
:class:`~ForwardRef` instances created by other means may not have any information
about their scope, so passing arguments to this method may be necessary to
evaluate them successfully.

*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.

.. important::

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 :const:`!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. This function should be used 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,
and must be a member of the :class:`Format` enum or its integer equivalent.

Returns a dict. :func:`!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 :class:`!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.

*eval_str* controls whether or not values of type :class:`!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 :class:`!str`.
(Note that :func:`!get_annotations` doesn't catch exceptions; if :func:`eval()`
raises an exception, it will unwind the stack past the :func:`!get_annotations`
call.)
* If *eval_str* is false (the default), values of type :class:`!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 :const:`!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`) or a :class:`functools.partial` object,
it is unwrapped until a non-wrapped function is found.

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 @@ -718,19 +718,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 @@ -1247,62 +1247,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
Loading
Loading