Skip to content

Commit be4b834

Browse files
Merge branch 'main' into patch-3
2 parents 8a94d1d + e69ff34 commit be4b834

27 files changed

+790
-173
lines changed

Doc/data/stable_abi.dat

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Doc/library/argparse.rst

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,11 +249,12 @@ The following sections describe how each of these are used.
249249
prog
250250
^^^^
251251

252-
By default, :class:`ArgumentParser` objects use ``sys.argv[0]`` to determine
252+
By default, :class:`ArgumentParser` objects use the base name
253+
(see :func:`os.path.basename`) of ``sys.argv[0]`` to determine
253254
how to display the name of the program in help messages. This default is almost
254-
always desirable because it will make the help messages match how the program was
255-
invoked on the command line. For example, consider a file named
256-
``myprogram.py`` with the following code::
255+
always desirable because it will make the help messages match the name that was
256+
used to invoke the program on the command line. For example, consider a file
257+
named ``myprogram.py`` with the following code::
257258

258259
import argparse
259260
parser = argparse.ArgumentParser()

Doc/library/typing.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,6 +1458,23 @@ These can be used as types in annotations. They all support subscription using
14581458
>>> X.__metadata__
14591459
('very', 'important', 'metadata')
14601460

1461+
* At runtime, if you want to retrieve the original
1462+
type wrapped by ``Annotated``, use the :attr:`!__origin__` attribute:
1463+
1464+
.. doctest::
1465+
1466+
>>> from typing import Annotated, get_origin
1467+
>>> Password = Annotated[str, "secret"]
1468+
>>> Password.__origin__
1469+
<class 'str'>
1470+
1471+
Note that using :func:`get_origin` will return ``Annotated`` itself:
1472+
1473+
.. doctest::
1474+
1475+
>>> get_origin(Password)
1476+
typing.Annotated
1477+
14611478
.. seealso::
14621479

14631480
:pep:`593` - Flexible function and variable annotations
@@ -3298,6 +3315,7 @@ Introspection helpers
32983315
assert get_origin(str) is None
32993316
assert get_origin(Dict[str, int]) is dict
33003317
assert get_origin(Union[int, str]) is Union
3318+
assert get_origin(Annotated[str, "metadata"]) is Annotated
33013319
P = ParamSpec('P')
33023320
assert get_origin(P.args) is P
33033321
assert get_origin(P.kwargs) is P

Doc/whatsnew/3.14.rst

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,91 @@ Summary -- Release highlights
7070
New Features
7171
============
7272

73+
.. _whatsnew-314-pep649:
74+
75+
PEP 649: Deferred Evaluation of Annotations
76+
-------------------------------------------
77+
78+
The :term:`annotations <annotation>` on functions, classes, and modules are no
79+
longer evaluated eagerly. Instead, annotations are stored in special-purpose
80+
:term:`annotate functions <annotate function>` and evaluated only when
81+
necessary. This is specified in :pep:`649` and :pep:`749`.
82+
83+
This change is designed to make annotations in Python more performant and more
84+
usable in most circumstances. The runtime cost for defining annotations is
85+
minimized, but it remains possible to introspect annotations at runtime.
86+
It is usually no longer necessary to enclose annotations in strings if they
87+
contain forward references.
88+
89+
The new :mod:`annotationlib` module provides tools for inspecting deferred
90+
annotations. Annotations may be evaluated in the :attr:`~annotationlib.Format.VALUE`
91+
format (which evaluates annotations to runtime values, similar to the behavior in
92+
earlier Python versions), the :attr:`~annotationlib.Format.FORWARDREF` format
93+
(which replaces undefined names with special markers), and the
94+
:attr:`~annotationlib.Format.SOURCE` format (which returns annotations as strings).
95+
96+
This example shows how these formats behave:
97+
98+
.. doctest::
99+
100+
>>> from annotationlib import get_annotations, Format
101+
>>> def func(arg: Undefined):
102+
... pass
103+
>>> get_annotations(func, format=Format.VALUE)
104+
Traceback (most recent call last):
105+
...
106+
NameError: name 'Undefined' is not defined
107+
>>> get_annotations(func, format=Format.FORWARDREF)
108+
{'arg': ForwardRef('Undefined')}
109+
>>> get_annotations(func, format=Format.SOURCE)
110+
{'arg': 'Undefined'}
111+
112+
Implications for annotated code
113+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
114+
115+
If you define annotations in your code (for example, for use with a static type
116+
checker), then this change probably does not affect you: you can keep
117+
writing annotations the same way you did with previous versions of Python.
118+
119+
You will likely be able to remove quoted strings in annotations, which are frequently
120+
used for forward references. Similarly, if you use ``from __future__ import annotations``
121+
to avoid having to write strings in annotations, you may well be able to
122+
remove that import. However, if you rely on third-party libraries that read annotations,
123+
those libraries may need changes to support unquoted annotations before they
124+
work as expected.
125+
126+
Implications for readers of ``__annotations__``
127+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
128+
129+
If your code reads the ``__annotations__`` attribute on objects, you may want
130+
to make changes in order to support code that relies on deferred evaluation of
131+
annotations. For example, you may want to use :func:`annotationlib.get_annotations`
132+
with the :attr:`~annotationlib.Format.FORWARDREF` format, as the :mod:`dataclasses`
133+
module now does.
134+
135+
Related changes
136+
^^^^^^^^^^^^^^^
137+
138+
The changes in Python 3.14 are designed to rework how ``__annotations__``
139+
works at runtime while minimizing breakage to code that contains
140+
annotations in source code and to code that reads ``__annotations__``. However,
141+
if you rely on undocumented details of the annotation behavior or on private
142+
functions in the standard library, there are many ways in which your code may
143+
not work in Python 3.14. To safeguard your code against future changes,
144+
use only the documented functionality of the :mod:`annotationlib` module.
145+
146+
``from __future__ import annotations``
147+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
148+
149+
In Python 3.7, :pep:`563` introduced the ``from __future__ import annotations``
150+
directive, which turns all annotations into strings. This directive is now
151+
considered deprecated and it is expected to be removed in a future version of Python.
152+
However, this removal will not happen until after Python 3.13, the last version of
153+
Python without deferred evaluation of annotations, reaches its end of life.
154+
In Python 3.14, the behavior of code using ``from __future__ import annotations``
155+
is unchanged.
156+
157+
73158
Improved Error Messages
74159
-----------------------
75160

@@ -109,7 +194,9 @@ Other Language Changes
109194
New Modules
110195
===========
111196

112-
* None yet.
197+
* :mod:`annotationlib`: For introspecting :term:`annotations <annotation>`.
198+
See :pep:`749` for more details.
199+
(Contributed by Jelle Zijlstra in :gh:`119180`.)
113200

114201

115202
Improved Modules
@@ -563,9 +650,10 @@ New Features
563650
Porting to Python 3.14
564651
----------------------
565652

566-
* In the limited C API 3.14 and newer, :c:func:`Py_TYPE` is now implemented as
567-
an opaque function call to hide implementation details.
568-
(Contributed by Victor Stinner in :gh:`120600`.)
653+
* In the limited C API 3.14 and newer, :c:func:`Py_TYPE` and
654+
:c:func:`Py_REFCNT` are now implemented as an opaque function call to hide
655+
implementation details.
656+
(Contributed by Victor Stinner in :gh:`120600` and :gh:`124127`.)
569657

570658

571659
Deprecated

Include/refcount.h

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -77,21 +77,29 @@ check by comparing the reference count field to the immortality reference count.
7777
#endif // Py_GIL_DISABLED
7878

7979

80-
static inline Py_ssize_t Py_REFCNT(PyObject *ob) {
81-
#if !defined(Py_GIL_DISABLED)
82-
return ob->ob_refcnt;
80+
// Py_REFCNT() implementation for the stable ABI
81+
PyAPI_FUNC(Py_ssize_t) Py_REFCNT(PyObject *ob);
82+
83+
#if defined(Py_LIMITED_API) && Py_LIMITED_API+0 >= 0x030e0000
84+
// Stable ABI implements Py_REFCNT() as a function call
85+
// on limited C API version 3.14 and newer.
8386
#else
84-
uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
85-
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
86-
return _Py_IMMORTAL_REFCNT;
87+
static inline Py_ssize_t _Py_REFCNT(PyObject *ob) {
88+
#if !defined(Py_GIL_DISABLED)
89+
return ob->ob_refcnt;
90+
#else
91+
uint32_t local = _Py_atomic_load_uint32_relaxed(&ob->ob_ref_local);
92+
if (local == _Py_IMMORTAL_REFCNT_LOCAL) {
93+
return _Py_IMMORTAL_REFCNT;
94+
}
95+
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
96+
return _Py_STATIC_CAST(Py_ssize_t, local) +
97+
Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT);
98+
#endif
8799
}
88-
Py_ssize_t shared = _Py_atomic_load_ssize_relaxed(&ob->ob_ref_shared);
89-
return _Py_STATIC_CAST(Py_ssize_t, local) +
90-
Py_ARITHMETIC_RIGHT_SHIFT(Py_ssize_t, shared, _Py_REF_SHARED_SHIFT);
91-
#endif
92-
}
93-
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
94-
# define Py_REFCNT(ob) Py_REFCNT(_PyObject_CAST(ob))
100+
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
101+
# define Py_REFCNT(ob) _Py_REFCNT(_PyObject_CAST(ob))
102+
#endif
95103
#endif
96104

97105

Lib/argparse.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1532,9 +1532,8 @@ def _get_positional_kwargs(self, dest, **kwargs):
15321532

15331533
# mark positional arguments as required if at least one is
15341534
# always required
1535-
if kwargs.get('nargs') not in [OPTIONAL, ZERO_OR_MORE]:
1536-
kwargs['required'] = True
1537-
if kwargs.get('nargs') == ZERO_OR_MORE and 'default' not in kwargs:
1535+
nargs = kwargs.get('nargs')
1536+
if nargs not in [OPTIONAL, ZERO_OR_MORE, REMAINDER, SUPPRESS, 0]:
15381537
kwargs['required'] = True
15391538

15401539
# return the keyword arguments with no option strings
@@ -1949,9 +1948,8 @@ def take_action(action, argument_strings, option_string=None):
19491948
argument_values = self._get_values(action, argument_strings)
19501949

19511950
# error if this argument is not allowed with other previously
1952-
# seen arguments, assuming that actions that use the default
1953-
# value don't really count as "present"
1954-
if argument_values is not action.default:
1951+
# seen arguments
1952+
if action.option_strings or argument_strings:
19551953
seen_non_default_actions.add(action)
19561954
for conflict_action in action_conflicts.get(action, []):
19571955
if conflict_action in seen_non_default_actions:
@@ -2069,11 +2067,15 @@ def consume_positionals(start_index):
20692067
# and add the Positional and its args to the list
20702068
for action, arg_count in zip(positionals, arg_counts):
20712069
args = arg_strings[start_index: start_index + arg_count]
2072-
# Strip out the first '--' if it is not in PARSER or REMAINDER arg.
2073-
if (action.nargs not in [PARSER, REMAINDER]
2074-
and arg_strings_pattern.find('-', start_index,
2070+
# Strip out the first '--' if it is not in REMAINDER arg.
2071+
if action.nargs == PARSER:
2072+
if arg_strings_pattern[start_index] == '-':
2073+
assert args[0] == '--'
2074+
args.remove('--')
2075+
elif action.nargs != REMAINDER:
2076+
if (arg_strings_pattern.find('-', start_index,
20752077
start_index + arg_count) >= 0):
2076-
args.remove('--')
2078+
args.remove('--')
20772079
start_index += arg_count
20782080
if args and action.deprecated and action.dest not in warned:
20792081
self._warning(_("argument '%(argument_name)s' is deprecated") %

Lib/test/support/script_helper.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -234,9 +234,13 @@ def make_script(script_dir, script_basename, source, omit_suffix=False):
234234
if not omit_suffix:
235235
script_filename += os.extsep + 'py'
236236
script_name = os.path.join(script_dir, script_filename)
237-
# The script should be encoded to UTF-8, the default string encoding
238-
with open(script_name, 'w', encoding='utf-8') as script_file:
239-
script_file.write(source)
237+
if isinstance(source, str):
238+
# The script should be encoded to UTF-8, the default string encoding
239+
with open(script_name, 'w', encoding='utf-8') as script_file:
240+
script_file.write(source)
241+
else:
242+
with open(script_name, 'wb') as script_file:
243+
script_file.write(source)
240244
importlib.invalidate_caches()
241245
return script_name
242246

0 commit comments

Comments
 (0)