Skip to content

Commit 044a104

Browse files
authoredOct 6, 2020
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

+3-7
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

+17
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

+10-3
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

+17-2
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

-6
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

-32
This file was deleted.

‎Lib/test/dataclass_module_2.py

-6
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

-32
This file was deleted.

‎Lib/test/dataclass_textanno.py

-2
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)
Please sign in to comment.