Skip to content

Commit 044a104

Browse files
authored
bpo-38605: Make 'from __future__ import annotations' the default (GH-20434)
The hard part was making all the tests pass; there are some subtle issues here, because apparently the future import wasn't tested very thoroughly in previous Python versions. For example, `inspect.signature()` returned type objects normally (except for forward references), but strings with the future import. We changed it to try and return type objects by calling `typing.get_type_hints()`, but fall back on returning strings if that function fails (which it may do if there are future references in the annotations that require passing in a specific namespace to resolve).
1 parent bef7d29 commit 044a104

27 files changed

+404
-300
lines changed

Doc/reference/compound_stmts.rst

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -610,13 +610,9 @@ following the parameter name. Any parameter may have an annotation, even those
610610
``*identifier`` or ``**identifier``. Functions may have "return" annotation of
611611
the form "``-> expression``" after the parameter list. These annotations can be
612612
any valid Python expression. The presence of annotations does not change the
613-
semantics of a function. The annotation values are available as values of
614-
a dictionary keyed by the parameters' names in the :attr:`__annotations__`
615-
attribute of the function object. If the ``annotations`` import from
616-
:mod:`__future__` is used, annotations are preserved as strings at runtime which
617-
enables postponed evaluation. Otherwise, they are evaluated when the function
618-
definition is executed. In this case annotations may be evaluated in
619-
a different order than they appear in the source code.
613+
semantics of a function. The annotation values are available as string values
614+
in a dictionary keyed by the parameters' names in the :attr:`__annotations__`
615+
attribute of the function object.
620616

621617
.. index:: pair: lambda; expression
622618

Doc/whatsnew/3.10.rst

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

73+
.. _whatsnew310-pep563:
74+
75+
PEP 563: Postponed Evaluation of Annotations Becomes Default
76+
------------------------------------------------------------
77+
78+
In Python 3.7, postponed evaluation of annotations was added,
79+
to be enabled with a ``from __future__ import annotations``
80+
directive. In 3.10 this became the default behavior, even
81+
without that future directive. With this being default, all
82+
annotations stored in :attr:`__annotations__` will be strings.
83+
If needed, annotations can be resolved at runtime using
84+
:func:`typing.get_type_hints`. See :pep:`563` for a full
85+
description. Also, the :func:`inspect.signature` will try to
86+
resolve types from now on, and when it fails it will fall back to
87+
showing the string annotations. (Contributed by Batuhan Taskaya
88+
in :issue:`38605`.)
89+
7390
* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
7491
number of ones in the binary expansion of a given integer, also known
7592
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)

Lib/dataclasses.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -399,8 +399,10 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
399399

400400
ns = {}
401401
exec(txt, globals, ns)
402-
return ns['__create_fn__'](**locals)
403-
402+
func = ns['__create_fn__'](**locals)
403+
for arg, annotation in func.__annotations__.copy().items():
404+
func.__annotations__[arg] = locals[annotation]
405+
return func
404406

405407
def _field_assign(frozen, name, value, self_name):
406408
# If we're a frozen class, then assign to our fields in __init__
@@ -651,6 +653,11 @@ def _is_type(annotation, cls, a_module, a_type, is_type_predicate):
651653
# a eval() penalty for every single field of every dataclass
652654
# that's defined. It was judged not worth it.
653655

656+
# Strip away the extra quotes as a result of double-stringifying when the
657+
# 'annotations' feature became default.
658+
if annotation.startswith(("'", '"')) and annotation.endswith(("'", '"')):
659+
annotation = annotation[1:-1]
660+
654661
match = _MODULE_IDENTIFIER_RE.match(annotation)
655662
if match:
656663
ns = None
@@ -991,7 +998,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
991998
if not getattr(cls, '__doc__'):
992999
# Create a class doc-string.
9931000
cls.__doc__ = (cls.__name__ +
994-
str(inspect.signature(cls)).replace(' -> None', ''))
1001+
str(inspect.signature(cls)).replace(' -> NoneType', ''))
9951002

9961003
abc.update_abstractmethods(cls)
9971004

Lib/inspect.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import tokenize
4646
import token
4747
import types
48+
import typing
4849
import warnings
4950
import functools
5051
import builtins
@@ -1877,7 +1878,10 @@ def _signature_is_functionlike(obj):
18771878
code = getattr(obj, '__code__', None)
18781879
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
18791880
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here
1880-
annotations = getattr(obj, '__annotations__', None)
1881+
try:
1882+
annotations = _get_type_hints(obj)
1883+
except AttributeError:
1884+
annotations = None
18811885

18821886
return (isinstance(code, types.CodeType) and
18831887
isinstance(name, str) and
@@ -2118,6 +2122,16 @@ def p(name_node, default_node, default=empty):
21182122

21192123
return cls(parameters, return_annotation=cls.empty)
21202124

2125+
def _get_type_hints(func):
2126+
try:
2127+
return typing.get_type_hints(func)
2128+
except Exception:
2129+
# First, try to use the get_type_hints to resolve
2130+
# annotations. But for keeping the behavior intact
2131+
# if there was a problem with that (like the namespace
2132+
# can't resolve some annotation) continue to use
2133+
# string annotations
2134+
return func.__annotations__
21212135

21222136
def _signature_from_builtin(cls, func, skip_bound_arg=True):
21232137
"""Private helper function to get signature for
@@ -2161,7 +2175,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
21612175
positional = arg_names[:pos_count]
21622176
keyword_only_count = func_code.co_kwonlyargcount
21632177
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
2164-
annotations = func.__annotations__
2178+
annotations = _get_type_hints(func)
2179+
21652180
defaults = func.__defaults__
21662181
kwdefaults = func.__kwdefaults__
21672182

Lib/test/dataclass_module_1.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
#from __future__ import annotations
2-
USING_STRINGS = False
3-
4-
# dataclass_module_1.py and dataclass_module_1_str.py are identical
5-
# except only the latter uses string annotations.
6-
71
import dataclasses
82
import typing
93

Lib/test/dataclass_module_1_str.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

Lib/test/dataclass_module_2.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,3 @@
1-
#from __future__ import annotations
2-
USING_STRINGS = False
3-
4-
# dataclass_module_2.py and dataclass_module_2_str.py are identical
5-
# except only the latter uses string annotations.
6-
71
from dataclasses import dataclass, InitVar
82
from typing import ClassVar
93

Lib/test/dataclass_module_2_str.py

Lines changed: 0 additions & 32 deletions
This file was deleted.

Lib/test/dataclass_textanno.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
from __future__ import annotations
2-
31
import dataclasses
42

53

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy