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).
This commit is contained in:
Batuhan Taskaya 2020-10-06 23:03:02 +03:00 committed by GitHub
parent bef7d299eb
commit 044a1048ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 403 additions and 299 deletions

View File

@ -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 ``*identifier`` or ``**identifier``. Functions may have "return" annotation of
the form "``-> expression``" after the parameter list. These annotations can be the form "``-> expression``" after the parameter list. These annotations can be
any valid Python expression. The presence of annotations does not change the any valid Python expression. The presence of annotations does not change the
semantics of a function. The annotation values are available as values of semantics of a function. The annotation values are available as string values
a dictionary keyed by the parameters' names in the :attr:`__annotations__` in a dictionary keyed by the parameters' names in the :attr:`__annotations__`
attribute of the function object. If the ``annotations`` import from attribute of the function object.
: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.
.. index:: pair: lambda; expression .. index:: pair: lambda; expression

View File

@ -70,6 +70,23 @@ Summary -- Release highlights
New Features 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 * 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 number of ones in the binary expansion of a given integer, also known
as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.) as the population count. (Contributed by Niklas Fiekas in :issue:`29882`.)

View File

@ -399,8 +399,10 @@ def _create_fn(name, args, body, *, globals=None, locals=None,
ns = {} ns = {}
exec(txt, globals, 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): def _field_assign(frozen, name, value, self_name):
# If we're a frozen class, then assign to our fields in __init__ # 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):
# a eval() penalty for every single field of every dataclass # a eval() penalty for every single field of every dataclass
# that's defined. It was judged not worth it. # 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) match = _MODULE_IDENTIFIER_RE.match(annotation)
if match: if match:
ns = None ns = None
@ -991,7 +998,7 @@ def _process_class(cls, init, repr, eq, order, unsafe_hash, frozen):
if not getattr(cls, '__doc__'): if not getattr(cls, '__doc__'):
# Create a class doc-string. # Create a class doc-string.
cls.__doc__ = (cls.__name__ + cls.__doc__ = (cls.__name__ +
str(inspect.signature(cls)).replace(' -> None', '')) str(inspect.signature(cls)).replace(' -> NoneType', ''))
abc.update_abstractmethods(cls) abc.update_abstractmethods(cls)

View File

@ -45,6 +45,7 @@ import sys
import tokenize import tokenize
import token import token
import types import types
import typing
import warnings import warnings
import functools import functools
import builtins import builtins
@ -1877,7 +1878,10 @@ def _signature_is_functionlike(obj):
code = getattr(obj, '__code__', None) code = getattr(obj, '__code__', None)
defaults = getattr(obj, '__defaults__', _void) # Important to use _void ... defaults = getattr(obj, '__defaults__', _void) # Important to use _void ...
kwdefaults = getattr(obj, '__kwdefaults__', _void) # ... and not None here 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 return (isinstance(code, types.CodeType) and
isinstance(name, str) and isinstance(name, str) and
@ -2118,6 +2122,16 @@ def _signature_fromstr(cls, obj, s, skip_bound_arg=True):
return cls(parameters, return_annotation=cls.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): def _signature_from_builtin(cls, func, skip_bound_arg=True):
"""Private helper function to get signature for """Private helper function to get signature for
@ -2161,7 +2175,8 @@ def _signature_from_function(cls, func, skip_bound_arg=True):
positional = arg_names[:pos_count] positional = arg_names[:pos_count]
keyword_only_count = func_code.co_kwonlyargcount keyword_only_count = func_code.co_kwonlyargcount
keyword_only = arg_names[pos_count:pos_count + keyword_only_count] keyword_only = arg_names[pos_count:pos_count + keyword_only_count]
annotations = func.__annotations__ annotations = _get_type_hints(func)
defaults = func.__defaults__ defaults = func.__defaults__
kwdefaults = func.__kwdefaults__ kwdefaults = func.__kwdefaults__

View File

@ -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 dataclasses
import typing import typing

View File

@ -1,32 +0,0 @@
from __future__ import annotations
USING_STRINGS = True
# dataclass_module_1.py and dataclass_module_1_str.py are identical
# except only the latter uses string annotations.
import dataclasses
import typing
T_CV2 = typing.ClassVar[int]
T_CV3 = typing.ClassVar
T_IV2 = dataclasses.InitVar[int]
T_IV3 = dataclasses.InitVar
@dataclasses.dataclass
class CV:
T_CV4 = typing.ClassVar
cv0: typing.ClassVar[int] = 20
cv1: typing.ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclasses.dataclass
class IV:
T_IV4 = dataclasses.InitVar
iv0: dataclasses.InitVar[int]
iv1: dataclasses.InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

View File

@ -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 dataclasses import dataclass, InitVar
from typing import ClassVar from typing import ClassVar

View File

@ -1,32 +0,0 @@
from __future__ import annotations
USING_STRINGS = True
# 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
T_CV2 = ClassVar[int]
T_CV3 = ClassVar
T_IV2 = InitVar[int]
T_IV3 = InitVar
@dataclass
class CV:
T_CV4 = ClassVar
cv0: ClassVar[int] = 20
cv1: ClassVar = 30
cv2: T_CV2
cv3: T_CV3
not_cv4: T_CV4 # When using string annotations, this field is not recognized as a ClassVar.
@dataclass
class IV:
T_IV4 = InitVar
iv0: InitVar[int]
iv1: InitVar
iv2: T_IV2
iv3: T_IV3
not_iv4: T_IV4 # When using string annotations, this field is not recognized as an InitVar.

View File

@ -1,5 +1,3 @@
from __future__ import annotations
import dataclasses import dataclasses

View File

@ -0,0 +1,228 @@
import unittest
import sys
from textwrap import dedent
class PostponedAnnotationsTestCase(unittest.TestCase):
template = dedent(
"""
def f() -> {ann}:
...
def g(arg: {ann}) -> None:
...
async def f2() -> {ann}:
...
async def g2(arg: {ann}) -> None:
...
var: {ann}
var2: {ann} = None
"""
)
def getActual(self, annotation):
scope = {}
exec(self.template.format(ann=annotation), {}, scope)
func_ret_ann = scope['f'].__annotations__['return']
func_arg_ann = scope['g'].__annotations__['arg']
async_func_ret_ann = scope['f2'].__annotations__['return']
async_func_arg_ann = scope['g2'].__annotations__['arg']
var_ann1 = scope['__annotations__']['var']
var_ann2 = scope['__annotations__']['var2']
self.assertEqual(func_ret_ann, func_arg_ann)
self.assertEqual(func_ret_ann, async_func_ret_ann)
self.assertEqual(func_ret_ann, async_func_arg_ann)
self.assertEqual(func_ret_ann, var_ann1)
self.assertEqual(func_ret_ann, var_ann2)
return func_ret_ann
def assertAnnotationEqual(
self, annotation, expected=None, drop_parens=False, is_tuple=False,
):
actual = self.getActual(annotation)
if expected is None:
expected = annotation if not is_tuple else annotation[1:-1]
if drop_parens:
self.assertNotEqual(actual, expected)
actual = actual.replace("(", "").replace(")", "")
self.assertEqual(actual, expected)
def test_annotations(self):
eq = self.assertAnnotationEqual
eq('...')
eq("'some_string'")
eq("u'some_string'")
eq("b'\\xa3'")
eq('Name')
eq('None')
eq('True')
eq('False')
eq('1')
eq('1.0')
eq('1j')
eq('True or False')
eq('True or False or None')
eq('True and False')
eq('True and False and None')
eq('Name1 and Name2 or Name3')
eq('Name1 and (Name2 or Name3)')
eq('Name1 or Name2 and Name3')
eq('(Name1 or Name2) and Name3')
eq('Name1 and Name2 or Name3 and Name4')
eq('Name1 or Name2 and Name3 or Name4')
eq('a + b + (c + d)')
eq('a * b * (c * d)')
eq('(a ** b) ** c ** d')
eq('v1 << 2')
eq('1 >> v2')
eq('1 % finished')
eq('1 + v2 - v3 * 4 ^ 5 ** v6 / 7 // 8')
eq('not great')
eq('not not great')
eq('~great')
eq('+value')
eq('++value')
eq('-1')
eq('~int and not v1 ^ 123 + v2 | True')
eq('a + (not b)')
eq('lambda: None')
eq('lambda arg: None')
eq('lambda a=True: a')
eq('lambda a, b, c=True: a')
eq("lambda a, b, c=True, *, d=1 << v2, e='str': a")
eq("lambda a, b, c=True, *vararg, d, e='str', **kwargs: a + b")
eq("lambda a, /, b, c=True, *vararg, d, e='str', **kwargs: a + b")
eq('lambda x, /: x')
eq('lambda x=1, /: x')
eq('lambda x, /, y: x + y')
eq('lambda x=1, /, y=2: x + y')
eq('lambda x, /, y=1: x + y')
eq('lambda x, /, y=1, *, z=3: x + y + z')
eq('lambda x=1, /, y=2, *, z=3: x + y + z')
eq('lambda x=1, /, y=2, *, z: x + y + z')
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2: x + y + z + w + l + l2')
eq('lambda x=1, y=2, z=3, /, w=4, *, l, l2, **kwargs: x + y + z + w + l + l2')
eq('lambda x, /, y=1, *, z: x + y + z')
eq('lambda x: lambda y: x + y')
eq('1 if True else 2')
eq('str or None if int or True else str or bytes or None')
eq('str or None if (1 if True else 2) else str or bytes or None')
eq("0 if not x else 1 if x > 0 else -1")
eq("(1 if x > 0 else -1) if x else 0")
eq("{'2.7': dead, '3.7': long_live or die_hard}")
eq("{'2.7': dead, '3.7': long_live or die_hard, **{'3.6': verygood}}")
eq("{**a, **b, **c}")
eq("{'2.7', '3.6', '3.7', '3.8', '3.9', '4.0' if gilectomy else '3.10'}")
eq("{*a, *b, *c}")
eq("({'a': 'b'}, True or False, +value, 'string', b'bytes') or None")
eq("()")
eq("(a,)")
eq("(a, b)")
eq("(a, b, c)")
eq("(*a, *b, *c)")
eq("[]")
eq("[1, 2, 3, 4, 5, 6, 7, 8, 9, 10 or A, 11 or B, 12 or C]")
eq("[*a, *b, *c]")
eq("{i for i in (1, 2, 3)}")
eq("{i ** 2 for i in (1, 2, 3)}")
eq("{i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))}")
eq("{i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)}")
eq("[i for i in (1, 2, 3)]")
eq("[i ** 2 for i in (1, 2, 3)]")
eq("[i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c'))]")
eq("[i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3)]")
eq("(i for i in (1, 2, 3))")
eq("(i ** 2 for i in (1, 2, 3))")
eq("(i ** 2 for i, _ in ((1, 'a'), (2, 'b'), (3, 'c')))")
eq("(i ** 2 + j for i in (1, 2, 3) for j in (1, 2, 3))")
eq("{i: 0 for i in (1, 2, 3)}")
eq("{i: j for i, j in ((1, 'a'), (2, 'b'), (3, 'c'))}")
eq("[(x, y) for x, y in (a, b)]")
eq("[(x,) for x, in (a,)]")
eq("Python3 > Python2 > COBOL")
eq("Life is Life")
eq("call()")
eq("call(arg)")
eq("call(kwarg='hey')")
eq("call(arg, kwarg='hey')")
eq("call(arg, *args, another, kwarg='hey')")
eq("call(arg, another, kwarg='hey', **kwargs, kwarg2='ho')")
eq("lukasz.langa.pl")
eq("call.me(maybe)")
eq("1 .real")
eq("1.0.real")
eq("....__class__")
eq("list[str]")
eq("dict[str, int]")
eq("set[str,]")
eq("tuple[str, ...]")
eq("tuple[(str, *types)]")
eq("tuple[str, int, (str, int)]")
eq("tuple[(*int, str, str, (str, int))]")
eq("tuple[str, int, float, dict[str, int]]")
eq("slice[0]")
eq("slice[0:1]")
eq("slice[0:1:2]")
eq("slice[:]")
eq("slice[:-1]")
eq("slice[1:]")
eq("slice[::-1]")
eq("slice[:,]")
eq("slice[1:2,]")
eq("slice[1:2:3,]")
eq("slice[1:2, 1]")
eq("slice[1:2, 2, 3]")
eq("slice[()]")
eq("slice[a, b:c, d:e:f]")
eq("slice[(x for x in a)]")
eq('str or None if sys.version_info[0] > (3,) else str or bytes or None')
eq("f'f-string without formatted values is just a string'")
eq("f'{{NOT a formatted value}}'")
eq("f'some f-string with {a} {few():.2f} {formatted.values!r}'")
eq('''f"{f'{nested} inner'} outer"''')
eq("f'space between opening braces: { {a for a in (1, 2, 3)}}'")
eq("f'{(lambda x: x)}'")
eq("f'{(None if a else lambda x: x)}'")
eq("f'{x}'")
eq("f'{x!r}'")
eq("f'{x!a}'")
eq('(yield from outside_of_generator)')
eq('(yield)')
eq('(yield a + b)')
eq('await some.complicated[0].call(with_args=True or 1 is not 1)')
eq('[x for x in (a if b else c)]')
eq('[x for x in a if (b if c else d)]')
eq('f(x for x in a)')
eq('f(1, (x for x in a))')
eq('f((x for x in a), 2)')
eq('(((a)))', 'a')
eq('(((a, b)))', '(a, b)')
eq("(x := 10)")
eq("f'{(x := 10):=10}'")
eq("1 + 2")
eq("1 + 2 + 3")
def test_fstring_debug_annotations(self):
# f-strings with '=' don't round trip very well, so set the expected
# result explicitely.
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
self.assertAnnotationEqual("f'{x=:}'", expected="f'x={x:}'")
self.assertAnnotationEqual("f'{x=:.2f}'", expected="f'x={x:.2f}'")
self.assertAnnotationEqual("f'{x=!r}'", expected="f'x={x!r}'")
self.assertAnnotationEqual("f'{x=!a}'", expected="f'x={x!a}'")
self.assertAnnotationEqual("f'{x=!s:*^20}'", expected="f'x={x!s:*^20}'")
def test_infinity_numbers(self):
inf = "1e" + repr(sys.float_info.max_10_exp + 1)
infj = f"{inf}j"
self.assertAnnotationEqual("1e1000", expected=inf)
self.assertAnnotationEqual("1e1000j", expected=infj)
self.assertAnnotationEqual("-1e1000", expected=f"-{inf}")
self.assertAnnotationEqual("3+1e1000j", expected=f"3 + {infj}")
self.assertAnnotationEqual("(1e1000, 1e1000j)", expected=f"({inf}, {infj})")
self.assertAnnotationEqual("'inf'")
self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})")
self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))")
if __name__ == "__main__":
unittest.main()

View File

@ -91,10 +91,6 @@ class AsyncBadSyntaxTest(unittest.TestCase):
pass pass
""", """,
"""async def foo(a:await something()):
pass
""",
"""async def foo(): """async def foo():
def bar(): def bar():
[i async for i in els] [i async for i in els]
@ -299,10 +295,6 @@ class AsyncBadSyntaxTest(unittest.TestCase):
pass pass
""", """,
"""async def foo(a:await b):
pass
""",
"""def baz(): """def baz():
async def foo(a=await b): async def foo(a=await b):
pass pass

View File

@ -9,6 +9,7 @@ import pickle
import inspect import inspect
import builtins import builtins
import unittest import unittest
from textwrap import dedent
from unittest.mock import Mock from unittest.mock import Mock
from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional from typing import ClassVar, Any, List, Union, Tuple, Dict, Generic, TypeVar, Optional
from typing import get_type_hints from typing import get_type_hints
@ -562,17 +563,17 @@ class TestCase(unittest.TestCase):
self.assertEqual(len(the_fields), 3) self.assertEqual(len(the_fields), 3)
self.assertEqual(the_fields[0].name, 'x') self.assertEqual(the_fields[0].name, 'x')
self.assertEqual(the_fields[0].type, int) self.assertEqual(the_fields[0].type, 'int')
self.assertFalse(hasattr(C, 'x')) self.assertFalse(hasattr(C, 'x'))
self.assertTrue (the_fields[0].init) self.assertTrue (the_fields[0].init)
self.assertTrue (the_fields[0].repr) self.assertTrue (the_fields[0].repr)
self.assertEqual(the_fields[1].name, 'y') self.assertEqual(the_fields[1].name, 'y')
self.assertEqual(the_fields[1].type, str) self.assertEqual(the_fields[1].type, 'str')
self.assertIsNone(getattr(C, 'y')) self.assertIsNone(getattr(C, 'y'))
self.assertFalse(the_fields[1].init) self.assertFalse(the_fields[1].init)
self.assertTrue (the_fields[1].repr) self.assertTrue (the_fields[1].repr)
self.assertEqual(the_fields[2].name, 'z') self.assertEqual(the_fields[2].name, 'z')
self.assertEqual(the_fields[2].type, str) self.assertEqual(the_fields[2].type, 'str')
self.assertFalse(hasattr(C, 'z')) self.assertFalse(hasattr(C, 'z'))
self.assertTrue (the_fields[2].init) self.assertTrue (the_fields[2].init)
self.assertFalse(the_fields[2].repr) self.assertFalse(the_fields[2].repr)
@ -758,11 +759,11 @@ class TestCase(unittest.TestCase):
def validate_class(cls): def validate_class(cls):
# First, check __annotations__, even though they're not # First, check __annotations__, even though they're not
# function annotations. # function annotations.
self.assertEqual(cls.__annotations__['i'], int) self.assertEqual(cls.__annotations__['i'], 'int')
self.assertEqual(cls.__annotations__['j'], str) self.assertEqual(cls.__annotations__['j'], 'str')
self.assertEqual(cls.__annotations__['k'], F) self.assertEqual(cls.__annotations__['k'], 'F')
self.assertEqual(cls.__annotations__['l'], float) self.assertEqual(cls.__annotations__['l'], 'float')
self.assertEqual(cls.__annotations__['z'], complex) self.assertEqual(cls.__annotations__['z'], 'complex')
# Verify __init__. # Verify __init__.
@ -777,22 +778,22 @@ class TestCase(unittest.TestCase):
self.assertEqual(param.name, 'self') self.assertEqual(param.name, 'self')
param = next(params) param = next(params)
self.assertEqual(param.name, 'i') self.assertEqual(param.name, 'i')
self.assertIs (param.annotation, int) self.assertIs (param.annotation, 'int')
self.assertEqual(param.default, inspect.Parameter.empty) self.assertEqual(param.default, inspect.Parameter.empty)
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params) param = next(params)
self.assertEqual(param.name, 'j') self.assertEqual(param.name, 'j')
self.assertIs (param.annotation, str) self.assertIs (param.annotation, 'str')
self.assertEqual(param.default, inspect.Parameter.empty) self.assertEqual(param.default, inspect.Parameter.empty)
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params) param = next(params)
self.assertEqual(param.name, 'k') self.assertEqual(param.name, 'k')
self.assertIs (param.annotation, F) self.assertIs (param.annotation, 'F')
# Don't test for the default, since it's set to MISSING. # Don't test for the default, since it's set to MISSING.
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
param = next(params) param = next(params)
self.assertEqual(param.name, 'l') self.assertEqual(param.name, 'l')
self.assertIs (param.annotation, float) self.assertIs (param.annotation, 'float')
# Don't test for the default, since it's set to MISSING. # Don't test for the default, since it's set to MISSING.
self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD) self.assertEqual(param.kind, inspect.Parameter.POSITIONAL_OR_KEYWORD)
self.assertRaises(StopIteration, next, params) self.assertRaises(StopIteration, next, params)
@ -2806,13 +2807,10 @@ class TestDescriptors(unittest.TestCase):
class TestStringAnnotations(unittest.TestCase): class TestStringAnnotations(unittest.TestCase):
def test_classvar(self): def test_classvar(self):
# Some expressions recognized as ClassVar really aren't. But
# if you're using string annotations, it's not an exact
# science.
# These tests assume that both "import typing" and "from # These tests assume that both "import typing" and "from
# typing import *" have been run in this file. # typing import *" have been run in this file.
for typestr in ('ClassVar[int]', for typestr in ('ClassVar[int]',
'ClassVar [int]' 'ClassVar [int]',
' ClassVar [int]', ' ClassVar [int]',
'ClassVar', 'ClassVar',
' ClassVar ', ' ClassVar ',
@ -2823,17 +2821,15 @@ class TestStringAnnotations(unittest.TestCase):
'typing. ClassVar[str]', 'typing. ClassVar[str]',
'typing.ClassVar [str]', 'typing.ClassVar [str]',
'typing.ClassVar [ str]', 'typing.ClassVar [ str]',
# Double stringified
'"typing.ClassVar[int]"',
# Not syntactically valid, but these will # Not syntactically valid, but these will
# be treated as ClassVars. # be treated as ClassVars.
'typing.ClassVar.[int]', 'typing.ClassVar.[int]',
'typing.ClassVar+', 'typing.ClassVar+',
): ):
with self.subTest(typestr=typestr): with self.subTest(typestr=typestr):
@dataclass C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
class C:
x: typestr
# x is a ClassVar, so C() takes no args. # x is a ClassVar, so C() takes no args.
C() C()
@ -2854,9 +2850,7 @@ class TestStringAnnotations(unittest.TestCase):
'typingxClassVar[str]', 'typingxClassVar[str]',
): ):
with self.subTest(typestr=typestr): with self.subTest(typestr=typestr):
@dataclass C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
class C:
x: typestr
# x is not a ClassVar, so C() takes one arg. # x is not a ClassVar, so C() takes one arg.
self.assertEqual(C(10).x, 10) self.assertEqual(C(10).x, 10)
@ -2876,16 +2870,16 @@ class TestStringAnnotations(unittest.TestCase):
'dataclasses. InitVar[str]', 'dataclasses. InitVar[str]',
'dataclasses.InitVar [str]', 'dataclasses.InitVar [str]',
'dataclasses.InitVar [ str]', 'dataclasses.InitVar [ str]',
# Double stringified
'"dataclasses.InitVar[int]"',
# Not syntactically valid, but these will # Not syntactically valid, but these will
# be treated as InitVars. # be treated as InitVars.
'dataclasses.InitVar.[int]', 'dataclasses.InitVar.[int]',
'dataclasses.InitVar+', 'dataclasses.InitVar+',
): ):
with self.subTest(typestr=typestr): with self.subTest(typestr=typestr):
@dataclass C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
class C:
x: typestr
# x is an InitVar, so doesn't create a member. # x is an InitVar, so doesn't create a member.
with self.assertRaisesRegex(AttributeError, with self.assertRaisesRegex(AttributeError,
@ -2899,30 +2893,22 @@ class TestStringAnnotations(unittest.TestCase):
'typing.xInitVar[int]', 'typing.xInitVar[int]',
): ):
with self.subTest(typestr=typestr): with self.subTest(typestr=typestr):
@dataclass C = dataclass(type("C", (), {"__annotations__": {"x": typestr}}))
class C:
x: typestr
# x is not an InitVar, so there will be a member x. # x is not an InitVar, so there will be a member x.
self.assertEqual(C(10).x, 10) self.assertEqual(C(10).x, 10)
def test_classvar_module_level_import(self): def test_classvar_module_level_import(self):
from test import dataclass_module_1 from test import dataclass_module_1
from test import dataclass_module_1_str
from test import dataclass_module_2 from test import dataclass_module_2
from test import dataclass_module_2_str
for m in (dataclass_module_1, dataclass_module_1_str, for m in (dataclass_module_1,
dataclass_module_2, dataclass_module_2_str, dataclass_module_2):
):
with self.subTest(m=m): with self.subTest(m=m):
# There's a difference in how the ClassVars are # There's a difference in how the ClassVars are
# interpreted when using string annotations or # interpreted when using string annotations or
# not. See the imported modules for details. # not. See the imported modules for details.
if m.USING_STRINGS: c = m.CV(10)
c = m.CV(10)
else:
c = m.CV()
self.assertEqual(c.cv0, 20) self.assertEqual(c.cv0, 20)
@ -2938,14 +2924,9 @@ class TestStringAnnotations(unittest.TestCase):
# not an instance field. # not an instance field.
getattr(c, field_name) getattr(c, field_name)
if m.USING_STRINGS: # iv4 is interpreted as a normal field.
# iv4 is interpreted as a normal field. self.assertIn('not_iv4', c.__dict__)
self.assertIn('not_iv4', c.__dict__) self.assertEqual(c.not_iv4, 4)
self.assertEqual(c.not_iv4, 4)
else:
# iv4 is interpreted as an InitVar, so it
# won't exist on the instance.
self.assertNotIn('not_iv4', c.__dict__)
def test_text_annotations(self): def test_text_annotations(self):
from test import dataclass_textanno from test import dataclass_textanno

View File

@ -227,28 +227,26 @@ dis_annot_stmt_str = """\
2 0 SETUP_ANNOTATIONS 2 0 SETUP_ANNOTATIONS
2 LOAD_CONST 0 (1) 2 LOAD_CONST 0 (1)
4 STORE_NAME 0 (x) 4 STORE_NAME 0 (x)
6 LOAD_NAME 1 (int) 6 LOAD_CONST 1 ('int')
8 LOAD_NAME 2 (__annotations__) 8 LOAD_NAME 1 (__annotations__)
10 LOAD_CONST 1 ('x') 10 LOAD_CONST 2 ('x')
12 STORE_SUBSCR 12 STORE_SUBSCR
3 14 LOAD_NAME 3 (fun) 3 14 LOAD_CONST 3 ('fun(1)')
16 LOAD_CONST 0 (1) 16 LOAD_NAME 1 (__annotations__)
18 CALL_FUNCTION 1 18 LOAD_CONST 4 ('y')
20 LOAD_NAME 2 (__annotations__) 20 STORE_SUBSCR
22 LOAD_CONST 2 ('y')
24 STORE_SUBSCR
4 26 LOAD_CONST 0 (1) 4 22 LOAD_CONST 0 (1)
28 LOAD_NAME 4 (lst) 24 LOAD_NAME 2 (lst)
30 LOAD_NAME 3 (fun) 26 LOAD_NAME 3 (fun)
32 LOAD_CONST 3 (0) 28 LOAD_CONST 5 (0)
34 CALL_FUNCTION 1 30 CALL_FUNCTION 1
36 STORE_SUBSCR 32 STORE_SUBSCR
38 LOAD_NAME 1 (int) 34 LOAD_NAME 4 (int)
40 POP_TOP 36 POP_TOP
42 LOAD_CONST 4 (None) 38 LOAD_CONST 6 (None)
44 RETURN_VALUE 40 RETURN_VALUE
""" """
compound_stmt_str = """\ compound_stmt_str = """\

View File

@ -618,7 +618,7 @@ class TestUpdateWrapper(unittest.TestCase):
def _default_update(self): def _default_update(self):
def f(a:'This is a new annotation'): def f(a: int):
"""This is a test""" """This is a test"""
pass pass
f.attr = 'This is also a test' f.attr = 'This is also a test'
@ -635,7 +635,7 @@ class TestUpdateWrapper(unittest.TestCase):
self.assertEqual(wrapper.__name__, 'f') self.assertEqual(wrapper.__name__, 'f')
self.assertEqual(wrapper.__qualname__, f.__qualname__) self.assertEqual(wrapper.__qualname__, f.__qualname__)
self.assertEqual(wrapper.attr, 'This is also a test') self.assertEqual(wrapper.attr, 'This is also a test')
self.assertEqual(wrapper.__annotations__['a'], 'This is a new annotation') self.assertEqual(wrapper.__annotations__['a'], 'int')
self.assertNotIn('b', wrapper.__annotations__) self.assertNotIn('b', wrapper.__annotations__)
@unittest.skipIf(sys.flags.optimize >= 2, @unittest.skipIf(sys.flags.optimize >= 2,

View File

@ -362,7 +362,7 @@ class GrammarTests(unittest.TestCase):
z = 2 z = 2
def __init__(self, x): def __init__(self, x):
self.x: int = x self.x: int = x
self.assertEqual(C.__annotations__, {'_C__foo': int, 's': str}) self.assertEqual(C.__annotations__, {'_C__foo': 'int', 's': 'str'})
with self.assertRaises(NameError): with self.assertRaises(NameError):
class CBad: class CBad:
no_such_name_defined.attr: int = 0 no_such_name_defined.attr: int = 0
@ -378,15 +378,15 @@ class GrammarTests(unittest.TestCase):
return {'__annotations__': CNS()} return {'__annotations__': CNS()}
class CC(metaclass=CMeta): class CC(metaclass=CMeta):
XX: 'ANNOT' XX: 'ANNOT'
self.assertEqual(CC.__annotations__['xx'], 'ANNOT') self.assertEqual(CC.__annotations__['xx'], repr('ANNOT'))
def test_var_annot_module_semantics(self): def test_var_annot_module_semantics(self):
with self.assertRaises(AttributeError): with self.assertRaises(AttributeError):
print(test.__annotations__) print(test.__annotations__)
self.assertEqual(ann_module.__annotations__, self.assertEqual(ann_module.__annotations__,
{1: 2, 'x': int, 'y': str, 'f': typing.Tuple[int, int]}) {1: 2, 'x': 'int', 'y': 'str', 'f': 'Tuple[int, int]'})
self.assertEqual(ann_module.M.__annotations__, self.assertEqual(ann_module.M.__annotations__,
{'123': 123, 'o': type}) {'123': 123, 'o': 'type'})
self.assertEqual(ann_module2.__annotations__, {}) self.assertEqual(ann_module2.__annotations__, {})
def test_var_annot_in_module(self): def test_var_annot_in_module(self):
@ -405,7 +405,7 @@ class GrammarTests(unittest.TestCase):
exec("'docstring'\n" exec("'docstring'\n"
"__annotations__[1] = 2\n" "__annotations__[1] = 2\n"
"x: int = 5\n", gns, lns) "x: int = 5\n", gns, lns)
self.assertEqual(lns["__annotations__"], {1: 2, 'x': int}) self.assertEqual(lns["__annotations__"], {1: 2, 'x': 'int'})
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
gns['__annotations__'] gns['__annotations__']
@ -413,8 +413,8 @@ class GrammarTests(unittest.TestCase):
# tests with custom locals() and __annotations__ # tests with custom locals() and __annotations__
ns = {'__annotations__': CNS()} ns = {'__annotations__': CNS()}
exec('X: int; Z: str = "Z"; (w): complex = 1j', ns) exec('X: int; Z: str = "Z"; (w): complex = 1j', ns)
self.assertEqual(ns['__annotations__']['x'], int) self.assertEqual(ns['__annotations__']['x'], 'int')
self.assertEqual(ns['__annotations__']['z'], str) self.assertEqual(ns['__annotations__']['z'], 'str')
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
ns['__annotations__']['w'] ns['__annotations__']['w']
nonloc_ns = {} nonloc_ns = {}
@ -428,7 +428,7 @@ class GrammarTests(unittest.TestCase):
def __getitem__(self, item): def __getitem__(self, item):
return self._dct[item] return self._dct[item]
exec('x: int = 1', {}, CNS2()) exec('x: int = 1', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], int) self.assertEqual(nonloc_ns['__annotations__']['x'], 'int')
def test_var_annot_refleak(self): def test_var_annot_refleak(self):
# complex case: custom locals plus custom __annotations__ # complex case: custom locals plus custom __annotations__
@ -445,7 +445,7 @@ class GrammarTests(unittest.TestCase):
def __getitem__(self, item): def __getitem__(self, item):
return self._dct[item] return self._dct[item]
exec('X: str', {}, CNS2()) exec('X: str', {}, CNS2())
self.assertEqual(nonloc_ns['__annotations__']['x'], str) self.assertEqual(nonloc_ns['__annotations__']['x'], 'str')
def test_var_annot_rhs(self): def test_var_annot_rhs(self):
ns = {} ns = {}
@ -625,50 +625,46 @@ class GrammarTests(unittest.TestCase):
# argument annotation tests # argument annotation tests
def f(x) -> list: pass def f(x) -> list: pass
self.assertEqual(f.__annotations__, {'return': list}) self.assertEqual(f.__annotations__, {'return': 'list'})
def f(x: int): pass def f(x: int): pass
self.assertEqual(f.__annotations__, {'x': int}) self.assertEqual(f.__annotations__, {'x': 'int'})
def f(x: int, /): pass def f(x: int, /): pass
self.assertEqual(f.__annotations__, {'x': int}) self.assertEqual(f.__annotations__, {'x': 'int'})
def f(x: int = 34, /): pass def f(x: int = 34, /): pass
self.assertEqual(f.__annotations__, {'x': int}) self.assertEqual(f.__annotations__, {'x': 'int'})
def f(*x: str): pass def f(*x: str): pass
self.assertEqual(f.__annotations__, {'x': str}) self.assertEqual(f.__annotations__, {'x': 'str'})
def f(**x: float): pass def f(**x: float): pass
self.assertEqual(f.__annotations__, {'x': float}) self.assertEqual(f.__annotations__, {'x': 'float'})
def f(x, y: 1+2): pass
self.assertEqual(f.__annotations__, {'y': 3})
def f(x, y: 1+2, /): pass
self.assertEqual(f.__annotations__, {'y': 3})
def f(a, b: 1, c: 2, d): pass def f(a, b: 1, c: 2, d): pass
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2}) self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
def f(a, b: 1, /, c: 2, d): pass def f(a, b: 1, /, c: 2, d): pass
self.assertEqual(f.__annotations__, {'b': 1, 'c': 2}) self.assertEqual(f.__annotations__, {'b': '1', 'c': '2'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6): pass
self.assertEqual(f.__annotations__, self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'g': 6}) {'b': '1', 'c': '2', 'e': '3', 'g': '6'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10, def f(a, b: 1, c: 2, d, e: 3 = 4, f=5, *g: 6, h: 7, i=8, j: 9 = 10,
**k: 11) -> 12: pass **k: 11) -> 12: pass
self.assertEqual(f.__annotations__, self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'g': 6, 'h': 7, 'j': 9, {'b': '1', 'c': '2', 'e': '3', 'g': '6', 'h': '7', 'j': '9',
'k': 11, 'return': 12}) 'k': '11', 'return': '12'})
def f(a, b: 1, c: 2, d, e: 3 = 4, f: int = 5, /, *g: 6, h: 7, i=8, j: 9 = 10, def f(a, b: 1, c: 2, d, e: 3 = 4, f: int = 5, /, *g: 6, h: 7, i=8, j: 9 = 10,
**k: 11) -> 12: pass **k: 11) -> 12: pass
self.assertEqual(f.__annotations__, self.assertEqual(f.__annotations__,
{'b': 1, 'c': 2, 'e': 3, 'f': int, 'g': 6, 'h': 7, 'j': 9, {'b': '1', 'c': '2', 'e': '3', 'f': 'int', 'g': '6', 'h': '7', 'j': '9',
'k': 11, 'return': 12}) 'k': '11', 'return': '12'})
# Check for issue #20625 -- annotations mangling # Check for issue #20625 -- annotations mangling
class Spam: class Spam:
def f(self, *, __kw: 1): def f(self, *, __kw: 1):
pass pass
class Ham(Spam): pass class Ham(Spam): pass
self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': 1}) self.assertEqual(Spam.f.__annotations__, {'_Spam__kw': '1'})
self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': 1}) self.assertEqual(Ham.f.__annotations__, {'_Spam__kw': '1'})
# Check for SF Bug #1697248 - mixing decorators and a return annotation # Check for SF Bug #1697248 - mixing decorators and a return annotation
def null(x): return x def null(x): return x
@null @null
def f(x) -> list: pass def f(x) -> list: pass
self.assertEqual(f.__annotations__, {'return': list}) self.assertEqual(f.__annotations__, {'return': 'list'})
# Test expressions as decorators (PEP 614): # Test expressions as decorators (PEP 614):
@False or null @False or null
@ -1116,8 +1112,6 @@ class GrammarTests(unittest.TestCase):
# Not allowed at class scope # Not allowed at class scope
check_syntax_error(self, "class foo:yield 1") check_syntax_error(self, "class foo:yield 1")
check_syntax_error(self, "class foo:yield from ()") check_syntax_error(self, "class foo:yield from ()")
# Check annotation refleak on SyntaxError
check_syntax_error(self, "def g(a:(yield)): pass")
def test_yield_in_comprehensions(self): def test_yield_in_comprehensions(self):
# Check yield in comprehensions # Check yield in comprehensions

View File

@ -862,7 +862,7 @@ class TestClassesAndFunctions(unittest.TestCase):
self.assertFullArgSpecEquals(mod2.annotated, ['arg1'], self.assertFullArgSpecEquals(mod2.annotated, ['arg1'],
ann_e={'arg1' : list}, ann_e={'arg1' : list},
formatted='(arg1: list)') formatted="(arg1: list)")
self.assertFullArgSpecEquals(mod2.keyword_only_arg, [], self.assertFullArgSpecEquals(mod2.keyword_only_arg, [],
kwonlyargs_e=['arg'], kwonlyargs_e=['arg'],
formatted='(*, arg)') formatted='(*, arg)')
@ -2211,8 +2211,8 @@ class TestSignatureObject(unittest.TestCase):
pass pass
self.assertEqual(self.signature(test), self.assertEqual(self.signature(test),
((('a', ..., ..., "positional_or_keyword"), ((('a', ..., ..., "positional_or_keyword"),
('b', ..., 'foo', "positional_or_keyword")), ('b', ..., repr('foo'), "positional_or_keyword")),
123)) '123'))
def test_signature_on_wkwonly(self): def test_signature_on_wkwonly(self):
def test(*, a:float, b:str) -> int: def test(*, a:float, b:str) -> int:
@ -2227,11 +2227,11 @@ class TestSignatureObject(unittest.TestCase):
pass pass
self.assertEqual(self.signature(test), self.assertEqual(self.signature(test),
((('a', ..., ..., "positional_or_keyword"), ((('a', ..., ..., "positional_or_keyword"),
('b', 10, 'foo', "positional_or_keyword"), ('b', 10, repr('foo'), "positional_or_keyword"),
('args', ..., 'bar', "var_positional"), ('args', ..., repr('bar'), "var_positional"),
('spam', ..., 'baz', "keyword_only"), ('spam', ..., repr('baz'), "keyword_only"),
('ham', 123, ..., "keyword_only"), ('ham', 123, ..., "keyword_only"),
('kwargs', ..., int, "var_keyword")), ('kwargs', ..., 'int', "var_keyword")),
...)) ...))
def test_signature_without_self(self): def test_signature_without_self(self):
@ -2640,12 +2640,12 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(self.signature(partial(partial(test, 1))), self.assertEqual(self.signature(partial(partial(test, 1))),
((('b', ..., ..., "positional_or_keyword"), ((('b', ..., ..., "positional_or_keyword"),
('c', ..., int, "positional_or_keyword")), ('c', ..., 'int', "positional_or_keyword")),
42)) '42'))
self.assertEqual(self.signature(partial(partial(test, 1), 2)), self.assertEqual(self.signature(partial(partial(test, 1), 2)),
((('c', ..., int, "positional_or_keyword"),), ((('c', ..., 'int', "positional_or_keyword"),),
42)) '42'))
psig = inspect.signature(partial(partial(test, 1), 2)) psig = inspect.signature(partial(partial(test, 1), 2))
@ -2764,12 +2764,12 @@ class TestSignatureObject(unittest.TestCase):
((('it', ..., ..., 'positional_or_keyword'), ((('it', ..., ..., 'positional_or_keyword'),
('a', ..., ..., 'positional_or_keyword'), ('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')), ('c', 1, ..., 'keyword_only')),
'spam')) repr('spam')))
self.assertEqual(self.signature(Spam().ham), self.assertEqual(self.signature(Spam().ham),
((('a', ..., ..., 'positional_or_keyword'), ((('a', ..., ..., 'positional_or_keyword'),
('c', 1, ..., 'keyword_only')), ('c', 1, ..., 'keyword_only')),
'spam')) repr('spam')))
class Spam: class Spam:
def test(self: 'anno', x): def test(self: 'anno', x):
@ -2778,7 +2778,7 @@ class TestSignatureObject(unittest.TestCase):
g = partialmethod(test, 1) g = partialmethod(test, 1)
self.assertEqual(self.signature(Spam.g), self.assertEqual(self.signature(Spam.g),
((('self', ..., 'anno', 'positional_or_keyword'),), ((('self', ..., repr('anno'), 'positional_or_keyword'),),
...)) ...))
def test_signature_on_fake_partialmethod(self): def test_signature_on_fake_partialmethod(self):
@ -3116,20 +3116,16 @@ class TestSignatureObject(unittest.TestCase):
with self.assertRaisesRegex(TypeError, 'unhashable type'): with self.assertRaisesRegex(TypeError, 'unhashable type'):
hash(inspect.signature(foo)) hash(inspect.signature(foo))
def foo(a) -> {}: pass
with self.assertRaisesRegex(TypeError, 'unhashable type'):
hash(inspect.signature(foo))
def test_signature_str(self): def test_signature_str(self):
def foo(a:int=1, *, b, c=None, **kwargs) -> 42: def foo(a:int=1, *, b, c=None, **kwargs) -> 42:
pass pass
self.assertEqual(str(inspect.signature(foo)), self.assertEqual(str(inspect.signature(foo)),
'(a: int = 1, *, b, c=None, **kwargs) -> 42') '(a: \'int\' = 1, *, b, c=None, **kwargs) -> \'42\'')
def foo(a:int=1, *args, b, c=None, **kwargs) -> 42: def foo(a:int=1, *args, b, c=None, **kwargs) -> 42:
pass pass
self.assertEqual(str(inspect.signature(foo)), self.assertEqual(str(inspect.signature(foo)),
'(a: int = 1, *args, b, c=None, **kwargs) -> 42') '(a: \'int\' = 1, *args, b, c=None, **kwargs) -> \'42\'')
def foo(): def foo():
pass pass
@ -3172,8 +3168,8 @@ class TestSignatureObject(unittest.TestCase):
self.assertIs(sig.return_annotation, None) self.assertIs(sig.return_annotation, None)
sig = sig.replace(return_annotation=sig.empty) sig = sig.replace(return_annotation=sig.empty)
self.assertIs(sig.return_annotation, sig.empty) self.assertIs(sig.return_annotation, sig.empty)
sig = sig.replace(return_annotation=42) sig = sig.replace(return_annotation='42')
self.assertEqual(sig.return_annotation, 42) self.assertEqual(sig.return_annotation, '42')
self.assertEqual(sig, inspect.signature(test)) self.assertEqual(sig, inspect.signature(test))
def test_signature_on_mangled_parameters(self): def test_signature_on_mangled_parameters(self):
@ -3185,8 +3181,8 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(self.signature(Spam.foo), self.assertEqual(self.signature(Spam.foo),
((('self', ..., ..., "positional_or_keyword"), ((('self', ..., ..., "positional_or_keyword"),
('_Spam__p1', 2, 1, "positional_or_keyword"), ('_Spam__p1', 2, '1', "positional_or_keyword"),
('_Spam__p2', 3, 2, "keyword_only")), ('_Spam__p2', 3, '2', "keyword_only")),
...)) ...))
self.assertEqual(self.signature(Spam.foo), self.assertEqual(self.signature(Spam.foo),

View File

@ -39,7 +39,7 @@ class OpcodeTest(unittest.TestCase):
def test_use_existing_annotations(self): def test_use_existing_annotations(self):
ns = {'__annotations__': {1: 2}} ns = {'__annotations__': {1: 2}}
exec('x: int', ns) exec('x: int', ns)
self.assertEqual(ns['__annotations__'], {'x': int, 1: 2}) self.assertEqual(ns['__annotations__'], {'x': 'int', 1: 2})
def test_do_not_recreate_annotations(self): def test_do_not_recreate_annotations(self):
# Don't rely on the existence of the '__annotations__' global. # Don't rely on the existence of the '__annotations__' global.

View File

@ -302,14 +302,14 @@ class PositionalOnlyTestCase(unittest.TestCase):
def f(x: int, /): ... def f(x: int, /): ...
return f return f
assert inner_has_pos_only().__annotations__ == {'x': int} assert inner_has_pos_only().__annotations__ == {'x': 'int'}
class Something: class Something:
def method(self): def method(self):
def f(x: int, /): ... def f(x: int, /): ...
return f return f
assert Something().method().__annotations__ == {'x': int} assert Something().method().__annotations__ == {'x': 'int'}
def multiple_levels(): def multiple_levels():
def inner_has_pos_only(): def inner_has_pos_only():
@ -317,7 +317,7 @@ class PositionalOnlyTestCase(unittest.TestCase):
return f return f
return inner_has_pos_only() return inner_has_pos_only()
assert multiple_levels().__annotations__ == {'x': int} assert multiple_levels().__annotations__ == {'x': 'int'}
def test_same_keyword_as_positional_with_kwargs(self): def test_same_keyword_as_positional_with_kwargs(self):
def f(something,/,**kwargs): def f(something,/,**kwargs):
@ -429,17 +429,6 @@ class PositionalOnlyTestCase(unittest.TestCase):
self.assertEqual(C().method(), sentinel) self.assertEqual(C().method(), sentinel)
def test_annotations_constant_fold(self):
def g():
def f(x: not (int is int), /): ...
# without constant folding we end up with
# COMPARE_OP(is), IS_OP (0)
# with constant folding we should expect a IS_OP (1)
codes = [(i.opname, i.argval) for i in dis.get_instructions(g)]
self.assertNotIn(('UNARY_NOT', None), codes)
self.assertIn(('IS_OP', 1), codes)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()

View File

@ -81,7 +81,7 @@ CLASSES
|\x20\x20 |\x20\x20
| NO_MEANING = 'eggs' | NO_MEANING = 'eggs'
|\x20\x20 |\x20\x20
| __annotations__ = {'NO_MEANING': <class 'str'>} | __annotations__ = {'NO_MEANING': 'str'}
\x20\x20\x20\x20 \x20\x20\x20\x20
class C(builtins.object) class C(builtins.object)
| Methods defined here: | Methods defined here:
@ -194,7 +194,7 @@ Data descriptors defined here:<br>
Data and other attributes defined here:<br> Data and other attributes defined here:<br>
<dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl> <dl><dt><strong>NO_MEANING</strong> = 'eggs'</dl>
<dl><dt><strong>__annotations__</strong> = {'NO_MEANING': &lt;class 'str'&gt;}</dl> <dl><dt><strong>__annotations__</strong> = {'NO_MEANING': 'str'}</dl>
</td></tr></table> <p> </td></tr></table> <p>
<table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section"> <table width="100%%" cellspacing=0 cellpadding=2 border=0 summary="section">

View File

@ -752,14 +752,6 @@ Corner-cases that used to fail to raise the correct error:
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: cannot assign to __debug__ SyntaxError: cannot assign to __debug__
>>> def f(*args:(lambda __debug__:0)): pass
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> def f(**kwargs:(lambda __debug__:0)): pass
Traceback (most recent call last):
SyntaxError: cannot assign to __debug__
>>> with (lambda *:0): pass >>> with (lambda *:0): pass
Traceback (most recent call last): Traceback (most recent call last):
SyntaxError: named arguments must follow bare * SyntaxError: named arguments must follow bare *

View File

@ -671,8 +671,8 @@ class TypesTests(unittest.TestCase):
ForwardBefore = 'Forward' | T ForwardBefore = 'Forward' | T
def forward_after(x: ForwardAfter[int]) -> None: ... def forward_after(x: ForwardAfter[int]) -> None: ...
def forward_before(x: ForwardBefore[int]) -> None: ... def forward_before(x: ForwardBefore[int]) -> None: ...
assert typing.get_args(typing.get_type_hints(forward_after)['x']) == (int, Forward) assert typing.get_args(typing.get_type_hints(forward_after, localns=locals())['x']) == (int, Forward)
assert typing.get_args(typing.get_type_hints(forward_before)['x']) == (int, Forward) assert typing.get_args(typing.get_type_hints(forward_before, localns=locals())['x']) == (int, Forward)
def test_or_type_operator_with_Protocol(self): def test_or_type_operator_with_Protocol(self):
class Proto(typing.Protocol): class Proto(typing.Protocol):

View File

@ -349,7 +349,7 @@ class UnionTests(BaseTestCase):
def test_no_eval_union(self): def test_no_eval_union(self):
u = Union[int, str] u = Union[int, str]
def f(x: u): ... def f(x: u): ...
self.assertIs(get_type_hints(f)['x'], u) self.assertIs(get_type_hints(f, globals(), locals())['x'], u)
def test_function_repr_union(self): def test_function_repr_union(self):
def fun() -> int: ... def fun() -> int: ...
@ -2849,11 +2849,11 @@ class GetTypeHintTests(BaseTestCase):
self.assertEqual(gth(HasForeignBaseClass), self.assertEqual(gth(HasForeignBaseClass),
{'some_xrepr': XRepr, 'other_a': mod_generics_cache.A, {'some_xrepr': XRepr, 'other_a': mod_generics_cache.A,
'some_b': mod_generics_cache.B}) 'some_b': mod_generics_cache.B})
self.assertEqual(gth(XRepr.__new__), self.assertEqual(gth(XRepr),
{'x': int, 'y': int}) {'x': int, 'y': int})
self.assertEqual(gth(mod_generics_cache.B), self.assertEqual(gth(mod_generics_cache.B),
{'my_inner_a1': mod_generics_cache.B.A, {'my_inner_a1': mod_generics_cache.B.A,
'my_inner_a2': mod_generics_cache.B.A, 'my_inner_a2': mod_generics_cache.A,
'my_outer_a': mod_generics_cache.A}) 'my_outer_a': mod_generics_cache.A})
def test_respect_no_type_check(self): def test_respect_no_type_check(self):
@ -3641,7 +3641,7 @@ class NamedTupleTests(BaseTestCase):
self.assertEqual(tim.cool, 9000) self.assertEqual(tim.cool, 9000)
self.assertEqual(CoolEmployee.__name__, 'CoolEmployee') self.assertEqual(CoolEmployee.__name__, 'CoolEmployee')
self.assertEqual(CoolEmployee._fields, ('name', 'cool')) self.assertEqual(CoolEmployee._fields, ('name', 'cool'))
self.assertEqual(CoolEmployee.__annotations__, self.assertEqual(gth(CoolEmployee),
collections.OrderedDict(name=str, cool=int)) collections.OrderedDict(name=str, cool=int))
def test_annotation_usage_with_default(self): def test_annotation_usage_with_default(self):
@ -3655,7 +3655,7 @@ class NamedTupleTests(BaseTestCase):
self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault') self.assertEqual(CoolEmployeeWithDefault.__name__, 'CoolEmployeeWithDefault')
self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool')) self.assertEqual(CoolEmployeeWithDefault._fields, ('name', 'cool'))
self.assertEqual(CoolEmployeeWithDefault.__annotations__, self.assertEqual(gth(CoolEmployeeWithDefault),
dict(name=str, cool=int)) dict(name=str, cool=int))
self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0)) self.assertEqual(CoolEmployeeWithDefault._field_defaults, dict(cool=0))
@ -3823,7 +3823,7 @@ class TypedDictTests(BaseTestCase):
def test_py36_class_syntax_usage(self): def test_py36_class_syntax_usage(self):
self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D') self.assertEqual(LabelPoint2D.__name__, 'LabelPoint2D')
self.assertEqual(LabelPoint2D.__module__, __name__) self.assertEqual(LabelPoint2D.__module__, __name__)
self.assertEqual(LabelPoint2D.__annotations__, {'x': int, 'y': int, 'label': str}) self.assertEqual(gth(LabelPoint2D), {'x': int, 'y': int, 'label': str})
self.assertEqual(LabelPoint2D.__bases__, (dict,)) self.assertEqual(LabelPoint2D.__bases__, (dict,))
self.assertEqual(LabelPoint2D.__total__, True) self.assertEqual(LabelPoint2D.__total__, True)
self.assertNotIsSubclass(LabelPoint2D, typing.Sequence) self.assertNotIsSubclass(LabelPoint2D, typing.Sequence)
@ -3882,11 +3882,11 @@ class TypedDictTests(BaseTestCase):
assert BaseAnimal.__required_keys__ == frozenset(['name']) assert BaseAnimal.__required_keys__ == frozenset(['name'])
assert BaseAnimal.__optional_keys__ == frozenset([]) assert BaseAnimal.__optional_keys__ == frozenset([])
assert BaseAnimal.__annotations__ == {'name': str} assert gth(BaseAnimal) == {'name': str}
assert Animal.__required_keys__ == frozenset(['name']) assert Animal.__required_keys__ == frozenset(['name'])
assert Animal.__optional_keys__ == frozenset(['tail', 'voice']) assert Animal.__optional_keys__ == frozenset(['tail', 'voice'])
assert Animal.__annotations__ == { assert gth(Animal) == {
'name': str, 'name': str,
'tail': bool, 'tail': bool,
'voice': str, 'voice': str,
@ -3894,7 +3894,7 @@ class TypedDictTests(BaseTestCase):
assert Cat.__required_keys__ == frozenset(['name', 'fur_color']) assert Cat.__required_keys__ == frozenset(['name', 'fur_color'])
assert Cat.__optional_keys__ == frozenset(['tail', 'voice']) assert Cat.__optional_keys__ == frozenset(['tail', 'voice'])
assert Cat.__annotations__ == { assert gth(Cat) == {
'fur_color': str, 'fur_color': str,
'name': str, 'name': str,
'tail': bool, 'tail': bool,
@ -3915,7 +3915,7 @@ class IOTests(BaseTestCase):
def stuff(a: IO) -> AnyStr: def stuff(a: IO) -> AnyStr:
return a.readline() return a.readline()
a = stuff.__annotations__['a'] a = gth(stuff)['a']
self.assertEqual(a.__parameters__, (AnyStr,)) self.assertEqual(a.__parameters__, (AnyStr,))
def test_textio(self): def test_textio(self):
@ -3923,7 +3923,7 @@ class IOTests(BaseTestCase):
def stuff(a: TextIO) -> str: def stuff(a: TextIO) -> str:
return a.readline() return a.readline()
a = stuff.__annotations__['a'] a = gth(stuff)['a']
self.assertEqual(a.__parameters__, ()) self.assertEqual(a.__parameters__, ())
def test_binaryio(self): def test_binaryio(self):
@ -3931,7 +3931,7 @@ class IOTests(BaseTestCase):
def stuff(a: BinaryIO) -> bytes: def stuff(a: BinaryIO) -> bytes:
return a.readline() return a.readline()
a = stuff.__annotations__['a'] a = gth(stuff)['a']
self.assertEqual(a.__parameters__, ()) self.assertEqual(a.__parameters__, ())
def test_io_submodule(self): def test_io_submodule(self):

View File

@ -18,6 +18,7 @@ At large scale, the structure of the module is following:
""" """
from abc import abstractmethod, ABCMeta from abc import abstractmethod, ABCMeta
import ast
import collections import collections
import collections.abc import collections.abc
import contextlib import contextlib
@ -469,6 +470,13 @@ class ForwardRef(_Final, _root=True):
def __init__(self, arg, is_argument=True): def __init__(self, arg, is_argument=True):
if not isinstance(arg, str): if not isinstance(arg, str):
raise TypeError(f"Forward reference must be a string -- got {arg!r}") raise TypeError(f"Forward reference must be a string -- got {arg!r}")
# Double-stringified forward references is a result of activating
# the 'annotations' future by default. This way, we eliminate them in
# the runtime.
if arg.startswith(("'", '\"')) and arg.endswith(("'", '"')):
arg = arg[1:-1]
try: try:
code = compile(arg, '<string>', 'eval') code = compile(arg, '<string>', 'eval')
except SyntaxError: except SyntaxError:

View File

@ -0,0 +1,3 @@
Enable ``from __future__ import annotations`` (:pep:`563`) by default.
The values found in :attr:`__annotations__` dicts are now strings, e.g.
``{"x": "int"}`` instead of ``{"x": int}``.

View File

@ -392,7 +392,6 @@ static int astfold_expr(expr_ty node_, PyArena *ctx_, _PyASTOptimizeState *state
static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state); static int astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state); static int astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state); static int astfold_keyword(keyword_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state); static int astfold_withitem(withitem_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state); static int astfold_excepthandler(excepthandler_ty node_, PyArena *ctx_, _PyASTOptimizeState *state);
#define CALL(FUNC, TYPE, ARG) \ #define CALL(FUNC, TYPE, ARG) \
@ -595,25 +594,11 @@ astfold_comprehension(comprehension_ty node_, PyArena *ctx_, _PyASTOptimizeState
static int static int
astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) astfold_arguments(arguments_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{ {
CALL_SEQ(astfold_arg, arg, node_->posonlyargs);
CALL_SEQ(astfold_arg, arg, node_->args);
CALL_OPT(astfold_arg, arg_ty, node_->vararg);
CALL_SEQ(astfold_arg, arg, node_->kwonlyargs);
CALL_SEQ(astfold_expr, expr, node_->kw_defaults); CALL_SEQ(astfold_expr, expr, node_->kw_defaults);
CALL_OPT(astfold_arg, arg_ty, node_->kwarg);
CALL_SEQ(astfold_expr, expr, node_->defaults); CALL_SEQ(astfold_expr, expr, node_->defaults);
return 1; return 1;
} }
static int
astfold_arg(arg_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->annotation);
}
return 1;
}
static int static int
astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state) astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
{ {
@ -622,17 +607,11 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args); CALL(astfold_arguments, arguments_ty, node_->v.FunctionDef.args);
CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body); CALL(astfold_body, asdl_seq, node_->v.FunctionDef.body);
CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list); CALL_SEQ(astfold_expr, expr, node_->v.FunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.FunctionDef.returns);
}
break; break;
case AsyncFunctionDef_kind: case AsyncFunctionDef_kind:
CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args); CALL(astfold_arguments, arguments_ty, node_->v.AsyncFunctionDef.args);
CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body); CALL(astfold_body, asdl_seq, node_->v.AsyncFunctionDef.body);
CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list); CALL_SEQ(astfold_expr, expr, node_->v.AsyncFunctionDef.decorator_list);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL_OPT(astfold_expr, expr_ty, node_->v.AsyncFunctionDef.returns);
}
break; break;
case ClassDef_kind: case ClassDef_kind:
CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases); CALL_SEQ(astfold_expr, expr, node_->v.ClassDef.bases);
@ -656,9 +635,6 @@ astfold_stmt(stmt_ty node_, PyArena *ctx_, _PyASTOptimizeState *state)
break; break;
case AnnAssign_kind: case AnnAssign_kind:
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.target); CALL(astfold_expr, expr_ty, node_->v.AnnAssign.target);
if (!(state->ff_features & CO_FUTURE_ANNOTATIONS)) {
CALL(astfold_expr, expr_ty, node_->v.AnnAssign.annotation);
}
CALL_OPT(astfold_expr, expr_ty, node_->v.AnnAssign.value); CALL_OPT(astfold_expr, expr_ty, node_->v.AnnAssign.value);
break; break;
case For_kind: case For_kind:

View File

@ -2026,12 +2026,7 @@ compiler_visit_argannotation(struct compiler *c, identifier id,
{ {
if (annotation) { if (annotation) {
PyObject *mangled; PyObject *mangled;
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) { VISIT(c, annexpr, annotation);
VISIT(c, annexpr, annotation)
}
else {
VISIT(c, expr, annotation);
}
mangled = _Py_Mangle(c->u->u_private, id); mangled = _Py_Mangle(c->u->u_private, id);
if (!mangled) if (!mangled)
return 0; return 0;
@ -5261,12 +5256,7 @@ compiler_annassign(struct compiler *c, stmt_ty s)
if (s->v.AnnAssign.simple && if (s->v.AnnAssign.simple &&
(c->u->u_scope_type == COMPILER_SCOPE_MODULE || (c->u->u_scope_type == COMPILER_SCOPE_MODULE ||
c->u->u_scope_type == COMPILER_SCOPE_CLASS)) { c->u->u_scope_type == COMPILER_SCOPE_CLASS)) {
if (c->c_future->ff_features & CO_FUTURE_ANNOTATIONS) { VISIT(c, annexpr, s->v.AnnAssign.annotation);
VISIT(c, annexpr, s->v.AnnAssign.annotation)
}
else {
VISIT(c, expr, s->v.AnnAssign.annotation);
}
ADDOP_NAME(c, LOAD_NAME, __annotations__, names); ADDOP_NAME(c, LOAD_NAME, __annotations__, names);
mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id); mangled = _Py_Mangle(c->u->u_private, targ->v.Name.id);
ADDOP_LOAD_CONST_NEW(c, mangled); ADDOP_LOAD_CONST_NEW(c, mangled);

View File

@ -41,7 +41,7 @@ future_check_features(PyFutureFeatures *ff, stmt_ty s, PyObject *filename)
} else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) { } else if (strcmp(feature, FUTURE_GENERATOR_STOP) == 0) {
continue; continue;
} else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) { } else if (strcmp(feature, FUTURE_ANNOTATIONS) == 0) {
ff->ff_features |= CO_FUTURE_ANNOTATIONS; continue;
} else if (strcmp(feature, "braces") == 0) { } else if (strcmp(feature, "braces") == 0) {
PyErr_SetString(PyExc_SyntaxError, PyErr_SetString(PyExc_SyntaxError,
"not a chance"); "not a chance");