From 32752045d55fa8530e0d462d11ae2fab7b043fbe Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 23 Sep 2024 15:40:51 -0700 Subject: [PATCH 1/5] gh-119180: Add discussion of annotations to the 3.14 What's New --- Doc/whatsnew/3.14.rst | 76 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 53399aa4e50fa6..b76ded3df16abf 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -70,6 +70,78 @@ Summary -- Release highlights New Features ============ +.. _whatsnew-314-pep649: + +PEP 649: Deferred Evaluation of Annotations +------------------------------------------- + +The :term:`annotations ` on functions, classes, and modules are no +longer evaluated eagerly. Instead, annotations are stored in special-purpose +:term:`annotation functions ` and evaluated only when +necessary. This is specified in :pep:`649` and :pep:`749`. + +The new :mod:`annotationlib` module provides tools for inspecting deferred +annotations. Annotations may be evaluated in the :attr:`~annotationlib.Format.VALUE` +format (which evaluates annotations to runtime values, similar to the behavior in +earlier Python versions), the :attr:`~annotationlib.Format.FORWARDREF` format +(which replaces undefined names with special markers), and the +:attr:`~annotationlib.Format.SOURCE` format (which returns annotations as strings). + +This example shows how these formats behave:: + + from annotationlib import get_annotations, Format + + def func(arg: Undefined): + pass + + print(get_annotations(func, format=Format.VALUE)) # raises NameError + print(get_annotations(func, format=Format.FORWARDREF)) # {'arg': ForwardRef('Undefined')} + print(get_annotations(func, format=Format.SOURCE)) # {'arg': 'Undefined'} + +Implications if you write annotations in your code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you write annotations in your code (for example, for use with a static type +checker), then this change probably does not affect you: you can keep +writing annotations the same way you did in previous versions of Python. + +You will likely be able to remove quoted strings in annotations, which are frequently +used for forward references. Similarly, if you use ``from __future__ import annotations`` +to avoid having to write strings in annotations, you may well be able to +remove it. However, if you rely on third-party libraries that read annotations, +those libraries may need changes to support unquoted annotations. + +Implications if you read ``__annotations__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If your code reads the ``__annotations__`` attribute on objects, you may want +to make changes in order to support code that relies on deferred evaluation of +annotations. For example, you may want to use the :func:`annotationlib.get_annotations` +with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses` +module now does. + +Related changes +^^^^^^^^^^^^^^^ + +The changes in Python 3.14 are designed to minimize changes to code that contains +annotations in source code and to code that reads ``__annotations__``. However, +there are many changes to the supporting code, and if you rely on undocumented +details of the annotation behavior or on private functions in the standard library, +your code may not work in Python 3.14. To safeguard your code against future changes, +use only the documented functionality of the :mod:`annotationlib` module. + +``from __future__ import annotations`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +In Python 3.7, :pep:`563` introduced the ``from __future__ import annotations`` +directive, which turns all annotations into strings. This directive is now +considered deprecated and it is expected to be removed in a future version of Python. +However, this removal will not happen until after Python 3.13, the last version of +Python without deferred evaluation of annotations, loses support. +In Python 3.14, the behavior of code using ``from __future__ import annotations`` +does not change. + + Improved Error Messages ----------------------- @@ -109,7 +181,9 @@ Other Language Changes New Modules =========== -* None yet. +* :mod:`annotationlib`: For introspecting :term:`annotations `. + See :pep:`749` for more details. + (Contributed by Jelle Zijlstra in :gh:`119180`.) Improved Modules From 33ee31a6fc4b28ce7e0e7f2a343fcd4e434eb9f4 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 23 Sep 2024 16:35:20 -0700 Subject: [PATCH 2/5] annotate functions --- Doc/whatsnew/3.14.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index b76ded3df16abf..a608c7d951de97 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -77,7 +77,7 @@ PEP 649: Deferred Evaluation of Annotations The :term:`annotations ` on functions, classes, and modules are no longer evaluated eagerly. Instead, annotations are stored in special-purpose -:term:`annotation functions ` and evaluated only when +:term:`annotate functions ` and evaluated only when necessary. This is specified in :pep:`649` and :pep:`749`. The new :mod:`annotationlib` module provides tools for inspecting deferred From 697e13df8269025919eb790720dbdefe2cc7abb0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 23 Sep 2024 17:16:31 -0700 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/whatsnew/3.14.rst | 53 ++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index a608c7d951de97..81168cbc020fbe 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -87,47 +87,54 @@ earlier Python versions), the :attr:`~annotationlib.Format.FORWARDREF` format (which replaces undefined names with special markers), and the :attr:`~annotationlib.Format.SOURCE` format (which returns annotations as strings). -This example shows how these formats behave:: - - from annotationlib import get_annotations, Format - - def func(arg: Undefined): - pass - - print(get_annotations(func, format=Format.VALUE)) # raises NameError - print(get_annotations(func, format=Format.FORWARDREF)) # {'arg': ForwardRef('Undefined')} - print(get_annotations(func, format=Format.SOURCE)) # {'arg': 'Undefined'} +This example shows how these formats behave: + +.. doctest:: + + >>> from annotationlib import get_annotations, Format + >>> def func(arg: Undefined): + ... pass + >>> get_annotations(func, format=Format.VALUE) + Traceback (most recent call last): + ... + NameError: name 'Undefined' is not defined + >>> get_annotations(func, format=Format.FORWARDREF) + {'arg': ForwardRef('Undefined')} + >>> get_annotations(func, format=Format.SOURCE) + {'arg': 'Undefined'} Implications if you write annotations in your code ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you write annotations in your code (for example, for use with a static type +If you define annotations in your code (for example, for use with a static type checker), then this change probably does not affect you: you can keep -writing annotations the same way you did in previous versions of Python. +writing annotations the same way you did with previous versions of Python. You will likely be able to remove quoted strings in annotations, which are frequently used for forward references. Similarly, if you use ``from __future__ import annotations`` to avoid having to write strings in annotations, you may well be able to -remove it. However, if you rely on third-party libraries that read annotations, -those libraries may need changes to support unquoted annotations. +remove that import. However, if you rely on third-party libraries that read annotations, +those libraries may need changes to support unquoted annotations before they +work as expected. -Implications if you read ``__annotations__`` -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Implications for readers of ``__annotations__`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If your code reads the ``__annotations__`` attribute on objects, you may want to make changes in order to support code that relies on deferred evaluation of -annotations. For example, you may want to use the :func:`annotationlib.get_annotations` +annotations. For example, you may want to use :func:`annotationlib.get_annotations` with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses` module now does. Related changes ^^^^^^^^^^^^^^^ -The changes in Python 3.14 are designed to minimize changes to code that contains +The changes in Python 3.14 are designed to rework how ``__annotations__`` +works at runtime while minimizing breakage to code that contains annotations in source code and to code that reads ``__annotations__``. However, -there are many changes to the supporting code, and if you rely on undocumented -details of the annotation behavior or on private functions in the standard library, -your code may not work in Python 3.14. To safeguard your code against future changes, +if you rely on undocumented details of the annotation behavior or on private +functions in the standard library, there are many ways in which your code may +not work in Python 3.14. To safeguard your code against future changes, use only the documented functionality of the :mod:`annotationlib` module. ``from __future__ import annotations`` @@ -137,9 +144,9 @@ In Python 3.7, :pep:`563` introduced the ``from __future__ import annotations`` directive, which turns all annotations into strings. This directive is now considered deprecated and it is expected to be removed in a future version of Python. However, this removal will not happen until after Python 3.13, the last version of -Python without deferred evaluation of annotations, loses support. +Python without deferred evaluation of annotations, reaches its end of life. In Python 3.14, the behavior of code using ``from __future__ import annotations`` -does not change. +is unchanged. Improved Error Messages From 8e8e45e68ddd9d98b44e2622cd65a2fbc7399f3b Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 23 Sep 2024 17:26:32 -0700 Subject: [PATCH 4/5] Update Doc/whatsnew/3.14.rst --- Doc/whatsnew/3.14.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 81168cbc020fbe..2d8253d49d5a29 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -103,8 +103,8 @@ This example shows how these formats behave: >>> get_annotations(func, format=Format.SOURCE) {'arg': 'Undefined'} -Implications if you write annotations in your code -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Implications for annotated code +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ If you define annotations in your code (for example, for use with a static type checker), then this change probably does not affect you: you can keep From 143c965ec2da307ec41b2719e2150f7b6de1fbe7 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 23 Sep 2024 17:52:00 -0700 Subject: [PATCH 5/5] happy paragraph --- Doc/whatsnew/3.14.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Doc/whatsnew/3.14.rst b/Doc/whatsnew/3.14.rst index 2d8253d49d5a29..04f1a195bca561 100644 --- a/Doc/whatsnew/3.14.rst +++ b/Doc/whatsnew/3.14.rst @@ -80,6 +80,12 @@ longer evaluated eagerly. Instead, annotations are stored in special-purpose :term:`annotate functions ` and evaluated only when necessary. This is specified in :pep:`649` and :pep:`749`. +This change is designed to make annotations in Python more performant and more +usable in most circumstances. The runtime cost for defining annotations is +minimized, but it remains possible to introspect annotations at runtime. +It is usually no longer necessary to enclose annotations in strings if they +contain forward references. + The new :mod:`annotationlib` module provides tools for inspecting deferred annotations. Annotations may be evaluated in the :attr:`~annotationlib.Format.VALUE` format (which evaluates annotations to runtime values, similar to the behavior in