Skip to content

bpo-38605: Make postponed evaluation of annotations default #20434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 18 commits into from
Oct 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 3 additions & 7 deletions Doc/reference/compound_stmts.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,13 +610,9 @@ following the parameter name. Any parameter may have an annotation, even those
``*identifier`` or ``**identifier``. Functions may have "return" annotation of
the form "``-> expression``" after the parameter list. These annotations can be
any valid Python expression. The presence of annotations does not change the
semantics of a function. The annotation values are available as values of
a dictionary keyed by the parameters' names in the :attr:`__annotations__`
attribute of the function object. If the ``annotations`` import from
:mod:`__future__` is used, annotations are preserved as strings at runtime which
enables postponed evaluation. Otherwise, they are evaluated when the function
definition is executed. In this case annotations may be evaluated in
a different order than they appear in the source code.
semantics of a function. The annotation values are available as string values
in a dictionary keyed by the parameters' names in the :attr:`__annotations__`
attribute of the function object.

.. index:: pair: lambda; expression

Expand Down
17 changes: 17 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,23 @@ Summary -- Release highlights
New Features
============

.. _whatsnew310-pep563:

PEP 563: Postponed Evaluation of Annotations Becomes Default
------------------------------------------------------------

In Python 3.7, postponed evaluation of annotations was added,
to be enabled with a ``from __future__ import annotations``
directive. In 3.10 this became the default behavior, even
without that future directive. With this being default, all
annotations stored in :attr:`__annotations__` will be strings.
If needed, annotations can be resolved at runtime using
:func:`typing.get_type_hints`. See :pep:`563` for a full
description. Also, the :func:`inspect.signature` will try to
resolve types from now on, and when it fails it will fall back to
showing the string annotations. (Contributed by Batuhan Taskaya
in :issue:`38605`.)

* The :class:`int` type has a new method :meth:`int.bit_count`, returning the
number of ones in the binary expansion of a given integer, also known
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)
Expand Down
13 changes: 10 additions & 3 deletions Lib/dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,10 @@ def _create_fn(name, args, body, *, globals=None, locals=None,

ns = {}
exec(txt, globals, ns)
return ns['__create_fn__'](**locals)

func = ns['__create_fn__'](**locals)
for arg, annotation in func.__annotations__.copy().items():
func.__annotations__[arg] = locals[annotation]
return func

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

# Strip away the extra quotes as a result of double-stringifying when the
# 'annotations' feature became default.
if annotation.startswith(("'", '"')) and annotation.endswith(("'", '"')):
annotation = annotation[1:-1]

match = _MODULE_IDENTIFIER_RE.match(annotation)
if match:
ns = None
Expand Down Expand Up @@ -990,7 +997,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
if not getattr(cls, '__doc__'):
# Create a class doc-string.
cls.__doc__ = (cls.__name__ +
str(inspect.signature(cls)).replace(' -> None', ''))
str(inspect.signature(cls)).replace(' -> NoneType', ''))

return cls

Expand Down
19 changes: 17 additions & 2 deletions Lib/inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
import tokenize
import token
import types
import typing
import warnings
import functools
import builtins
Expand Down Expand Up @@ -1877,7 +1878,10 @@ def _signature_is_functionlike(obj):
code = getattr(obj, '__code__', None)
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here
annotations = getattr(obj, '__annotations__', None)
try:
annotations = _get_type_hints(obj)
except AttributeError:
annotations = None

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

return cls(parameters, return_annotation=cls.empty)

def _get_type_hints(func):
try:
return typing.get_type_hints(func)
except Exception:
# First, try to use the get_type_hints to resolve
# annotations. But for keeping the behavior intact
# if there was a problem with that (like the namespace
# can't resolve some annotation) continue to use
# string annotations
return func.__annotations__

def _signature_from_builtin(cls, func, skip_bound_arg=True):
"""Private helper function to get signature for
Expand Down Expand Up @@ -2161,7 +2175,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
annotations = func.__annotations__
annotations = _get_type_hints(func)

defaults = func.__defaults__
kwdefaults = func.__kwdefaults__

Expand Down
6 changes: 0 additions & 6 deletions Lib/test/dataclass_module_1.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#from __future__ import annotations
USING_STRINGS = False

# dataclass_module_1.py and dataclass_module_1_str.py are identical
# except only the latter uses string annotations.

import dataclasses
import typing

Expand Down
32 changes: 0 additions & 32 deletions Lib/test/dataclass_module_1_str.py

This file was deleted.

6 changes: 0 additions & 6 deletions Lib/test/dataclass_module_2.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#from __future__ import annotations
USING_STRINGS = False

# dataclass_module_2.py and dataclass_module_2_str.py are identical
# except only the latter uses string annotations.

from dataclasses import dataclass, InitVar
from typing import ClassVar

Expand Down
32 changes: 0 additions & 32 deletions Lib/test/dataclass_module_2_str.py

This file was deleted.

2 changes: 0 additions & 2 deletions Lib/test/dataclass_textanno.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from __future__ import annotations

import dataclasses


Expand Down
Loading